diff --git a/docs/feature-override/readme.md b/docs/feature-override/readme.md new file mode 100644 index 0000000000..95d18e41fc --- /dev/null +++ b/docs/feature-override/readme.md @@ -0,0 +1,164 @@ +# How to define a menu is available in `React` and its `PHP` override information. + +- [Introduction](#introduction) +- [1. Declare a menu is available in `React`.](#1-declare-a-menu-is-available-in-react) + - [Declare `React` menu in **Dokan Lite.**](#declare-react-menu-in-dokan-lite) + - [Declare `React` menu in **Dokan Pro** or **External Plugin**.](#declare-react-menu-in-dokan-pro-or-external-plugin) +- [2. Declare the Override templates for a React route.](#2-declare-the-override-templates-for-a-react-route) + - [Define the override templates for a React route in Dokan Lite.](#define-the-override-templates-for-a-react-route-in-dokan-lite) + - [Define the override templates for a React route in **Dokan Pro** or **External Plugin**.](#define-the-override-templates-for-a-react-route-in-dokan-pro-or-external-plugin) + - [Define the override templates array structure.](#define-the-override-templates-array-structure) +- [Manual Override from External Plugin](#manual-override-from-external-plugin) + +## Introduction +This document will help you to define a menu is available in `React` and its `PHP` override information. + + +## 1. Declare a menu is available in `React`. +To declare a menu is available in `React`, you need to define `route` property during the menu registration. + +### Declare `React` menu in **Dokan Lite**. +```php +// includes/functions-dashboard-navigation.php#L27-L66 +$menus = [ + 'dashboard' => [ + 'title' => __( 'Dashboard', 'dokan-lite' ), + 'icon' => '', + 'url' => dokan_get_navigation_url(), + 'pos' => 10, + 'permission' => 'dokan_view_overview_menu', + 'react_route' => '/', // <-- Define the route here + ], + 'products' => [ + 'title' => __( 'Products', 'dokan-lite' ), + 'icon' => '', + 'url' => dokan_get_navigation_url( 'products' ), + 'pos' => 30, + 'permission' => 'dokan_view_product_menu', + 'react_route' => 'products', // <-- Define the route here + ], + 'orders' => [ + 'title' => __( 'Orders', 'dokan-lite' ), + 'icon' => '', + 'url' => dokan_get_navigation_url( 'orders' ), + 'pos' => 50, + 'permission' => 'dokan_view_order_menu', + 'react_route' => 'orders', // <-- Define the route here + ], + 'withdraw' => [ + 'title' => __( 'Withdraw', 'dokan-lite' ), + 'icon' => '', + 'url' => dokan_get_navigation_url( 'withdraw' ), + 'pos' => 70, + 'permission' => 'dokan_view_withdraw_menu', + ], + 'settings' => [ + 'title' => __( 'Settings', 'dokan-lite' ), + 'icon' => '', + 'url' => dokan_get_navigation_url( 'settings/store' ), + 'pos' => 200, + ], + ]; +``` +In the above example, the `route` property is defined for each menu which we are indicating that the react route is available. +This will be used to determine if the menu is pointing to the react Application or to the Legacy PHP Route. + +The `route` property should be the same as the route defined in the `React` application in Router Array. + +It is important to note that the `route` property should be defined for the menu which is available in the `React` application. +If the `route` key is not defined for the menu, then the menu will be considered as a legacy menu and will be rendered using the PHP template. + + +### Declare `React` menu in **Dokan Pro** or **External Plugin**. + +```php +add_filter( 'dokan_get_dashboard_nav', function ( $menus ) { + $menus['products'] = [ + 'title' => __( 'Products', 'dokan-lite' ), + 'icon' => '', + 'url' => dokan_get_navigation_url( 'products' ), + 'pos' => 30, + 'permission' => 'dokan_view_product_menu', + 'react_route' => 'products', // <-- Define the route here + ]; + + return $menus; +} ); +``` + + +## 2. Declare the Override templates for a React route. +If you are writing a new feature or modifying an existing feature in the `React` application, you do not need to define the override templates for the `React` route. +But if you are modifying or migrating an existing feature written in PHP to the `React` application and you want that if some of the PHP template is overridden by the existing PHP template then the legacy PHP page will be displayed, then you need to define the override templates for the `React` route. +### Define the override templates for a React route in Dokan Lite. +```php +// VendorNavMenuChecker.php#L13-L26 +protected array $template_dependencies = [ + '' => [ + [ 'slug' => 'dashboard/dashboard' ], + [ 'slug' => 'dashboard/orders-widget' ], + [ 'slug' => 'dashboard/products-widget' ], + ], + 'products' => [ + [ 'slug' => 'products/products' ], + [ + 'slug' => 'products/products', + 'name' => 'listing', + ], + ], + ]; +``` + +In the above example, the `template_dependencies` property is defined for each route which we are indicating that the override templates are available for the route. This will be used to determine if the override templates are available for the route or not. + +### Define the override templates for a React route in **Dokan Pro** or **External Plugin**. +From Dokan Pro, we can add dependencies by using the filter `dokan_get_dashboard_nav_template_dependency`. + +```php +add_filter( 'dokan_get_dashboard_nav_template_dependency', function ( array $dependencies ) { + $dependencies['products'] = [ + [ 'slug' => 'products/products' ], + [ + 'slug' => 'products/products', + 'name' => 'listing', + ], + ]; + + return $dependencies; +} ); +``` +### Define the override templates array structure. +```php +/** +* @var array $template_dependencies Array of template dependencies for the route. + */ + +[ + 'route_name' => [ + [ + 'slug' => 'template-slug', + 'name' => 'template-name' // (Optional), + 'args' = [] // (Optional) + ], + ] +] +``` + +- **Slug:** The slug of the template file which is used to display the php content. +- **Name:** The name of the template file which is used to display the php content. (Optional) +- **Args:** The arguments which are passed to the template file in `dokan_get_template_part()` function. (Optional) + +## Manual Override from External Plugin +If you did not override any of the template file directly but you have override functionality by using `add_action` or `add_filter` then you can forcefully override the php route and template rendering for the route by using the `dokan_is_dashboard_nav_dependency_cleared` filter hook. + +```php + +add_filter( 'dokan_is_dashboard_nav_dependency_cleared', function ( $is_cleared, $route ) { + if ( 'products' === $route ) { + return true; + } + + return $is_cleared; +}, 10, 2 ); + +``` diff --git a/includes/Assets.php b/includes/Assets.php index 037716ed8e..9d8899f707 100644 --- a/includes/Assets.php +++ b/includes/Assets.php @@ -15,6 +15,11 @@ class Assets { public function __construct() { add_action( 'init', [ $this, 'register_all_scripts' ], 10 ); add_filter( 'dokan_localized_args', [ $this, 'conditional_localized_args' ] ); + add_action( + 'wp_footer', function () { + echo '
'; + } + ); if ( is_admin() ) { add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_admin_scripts' ], 10 ); diff --git a/includes/DependencyManagement/Providers/CommonServiceProvider.php b/includes/DependencyManagement/Providers/CommonServiceProvider.php index 03d0fb2858..149aada700 100644 --- a/includes/DependencyManagement/Providers/CommonServiceProvider.php +++ b/includes/DependencyManagement/Providers/CommonServiceProvider.php @@ -3,6 +3,7 @@ namespace WeDevs\Dokan\DependencyManagement\Providers; use WeDevs\Dokan\DependencyManagement\BaseServiceProvider; +use WeDevs\Dokan\VendorNavMenuChecker; class CommonServiceProvider extends BaseServiceProvider { /** @@ -65,5 +66,8 @@ public function register(): void { $this->getContainer() ->addShared( \WeDevs\Dokan\Privacy::class, \WeDevs\Dokan\Privacy::class ) ->addTag( self::TAG ); + $this->getContainer() + ->addShared( VendorNavMenuChecker::class, VendorNavMenuChecker::class ) + ->addTag( self::TAG ); } } diff --git a/includes/VendorNavMenuChecker.php b/includes/VendorNavMenuChecker.php new file mode 100644 index 0000000000..feb21b825a --- /dev/null +++ b/includes/VendorNavMenuChecker.php @@ -0,0 +1,214 @@ + [ ['slug' => 'template-slug', 'name' => 'template-name' (Optional), 'args' = [] (Optional) ] ] ] + */ + protected array $template_dependencies = []; + + /** + * Constructor. + */ + + public function __construct() { + add_filter( 'dokan_get_dashboard_nav', [ $this, 'convert_to_react_menu' ], 999 ); + add_filter( 'dokan_admin_notices', [ $this, 'display_notice' ] ); + } + + /** + * Get template dependencies. + * + * @since DOKAN_SINCE + * + * @return array + */ + public function get_template_dependencies(): array { + return apply_filters( 'dokan_get_dashboard_nav_template_dependency', $this->template_dependencies ); + } + + /** + * Convert menu items to react menu items + * + * @since DOKAN_SINCE + * + * @param array $menu_items Menu items. + * + * @return array + */ + + public function convert_to_react_menu( array $menu_items ): array { + return array_map( + function ( $item ) { + if ( ! empty( $item['react_route'] ) && $this->is_dependency_cleared( $item['react_route'] ) ) { + $item['url'] = $this->get_url_for_route( $item['react_route'] ); + } + if ( isset( $item['submenu'] ) ) { + $item['submenu'] = $this->convert_to_react_menu( $item['submenu'] ); + } + + return $item; + }, $menu_items + ); + } + + /** + * Check if the dependency is cleared or not. + * + * @since DOKAN_SINCE + * + * @param string $route Route. + * + * @return bool + */ + protected function is_dependency_cleared( string $route ): bool { + $clear = true; + $dependencies = $this->get_template_dependencies_resolutions(); + + if ( ! empty( $dependencies[ trim( $route, '/' ) ] ) ) { + $clear = false; + } + + return apply_filters( 'dokan_is_dashboard_nav_dependency_cleared', $clear, $route ); + } + + /** + * Get URL for the route. + * + * @since DOKAN_SINCE + * + * @param string $route Route. + * + * @return string + */ + protected function get_url_for_route( string $route ): string { + $route = apply_filters( 'dokan_get_url_for_react_route', $route ); + + return dokan_get_navigation_url() . '#' . trim( $route, '/' ); + } + + /** + * Get template dependencies resolutions. + * + * @since DOKAN_SINCE + * + * @return array + */ + protected function get_template_dependencies_resolutions(): array { + $dependencies = $this->get_template_dependencies(); + + $resolved_dependencies = array_map( + fn( $dependency_array ): array => array_filter( + array_map( + fn( $dependency ) => $this->get_overridden_template( + $dependency['slug'], + $dependency['name'] ?? '', + $dependency['args'] ?? [] + ), + $dependency_array + ) + ), + $dependencies + ); + + return apply_filters( 'dokan_get_dashboard_nav_template_dependency_resolutions', $resolved_dependencies ); + } + + /** + * Get overridden template part path. + * + * @since DOKAN_SINCE + * + * @param string $slug Template slug. + * @param string $name Template name. + * @param array $args Arguments. + * + * @return false|string Returns the template file if found otherwise false. + */ + protected function get_overridden_template( string $slug, string $name = '', array $args = [] ) { + $defaults = [ 'pro' => false ]; + $args = wp_parse_args( $args, $defaults ); + $template = ''; + $default_template = ''; + + // Look in yourtheme/dokan/slug-name.php and yourtheme/dokan/slug.php + $template_path = ! empty( $name ) ? "{$slug}-{$name}.php" : "{$slug}.php"; + $template = locate_template( [ dokan()->template_path() . $template_path ] ); + + /** + * Change template directory path filter + * + * @since 2.5.3 + */ + $template_path = apply_filters( 'dokan_set_template_path', dokan()->plugin_path() . '/templates', $template, $args ); + + // Get default slug-name.php + if ( ! $template && $name && file_exists( $template_path . "/{$slug}-{$name}.php" ) ) { + $template = $template_path . "/{$slug}-{$name}.php"; + $default_template = $template; + } + + if ( ! $template && ! $name && file_exists( $template_path . "/{$slug}.php" ) ) { + $template = $template_path . "/{$slug}.php"; + $default_template = $template; + } + + // Allow 3rd party plugin filter template file from their plugin + $template = apply_filters( 'dokan_get_template_part', $template, $slug, $name ); + + return $template && $default_template !== $template ? $template : false; + } + + /** + * List overridden templates. + * + * @since DOKAN_SINCE + * + * @return array + */ + public function list_overridden_templates(): array { + $dependencies = $this->get_template_dependencies_resolutions(); + $overridden_templates = []; + foreach ( $dependencies as $dependency ) { + $overridden_templates = array_merge( $overridden_templates, $dependency ); + } + + return $overridden_templates; + } + + /** + * Display notice if templates are overridden. + * + * @since DOKAN_SINCE + * + * @param array $notices Notices. + * + * @return array + */ + public function display_notice( array $notices ): array { + $overridden_templates = $this->list_overridden_templates(); + + if ( empty( $overridden_templates ) ) { + return $notices; + } + + $notice = sprintf( + /* translators: %s: overridden templates */ + __( 'The following templates are overridden:
%s', 'dokan-lite' ), + implode( ',
', $overridden_templates ) + ); + + $notices[] = [ + 'type' => 'alert', + 'title' => esc_html__( 'Some of Dokan Templates are overridden which limit new features.', 'dokan-lite' ), + 'description' => $notice, + ]; + + return $notices; + } +} diff --git a/src/Dashboard/index.tsx b/src/Dashboard/index.tsx index 4bf0eadc89..3706191244 100644 --- a/src/Dashboard/index.tsx +++ b/src/Dashboard/index.tsx @@ -1,41 +1,42 @@ -import {createRoot} from "@wordpress/element"; -import domReady from "@wordpress/dom-ready"; -import Layout from "../Layout"; -import getRoutes, { withRouter } from "../Routing"; -import { - createHashRouter, - RouterProvider, -} from "react-router-dom"; +import { createRoot } from '@wordpress/element'; +import domReady from '@wordpress/dom-ready'; +import Layout from '../Layout'; +import getRoutes, { withRouter } from '../Routing'; +import { createHashRouter, RouterProvider } from 'react-router-dom'; import './tailwind.scss'; const App = () => { const routes = getRoutes(); - const mapedRoutes = routes.map((route) => { + const mapedRoutes = routes.map( ( route ) => { const WithRouterComponent = withRouter( route.element ); return { path: route.path, - element: - - , - } - }); + element: ( + + + + ), + }; + } ); - const router = createHashRouter(mapedRoutes); + const router = createHashRouter( mapedRoutes ); - return ; -} + return ( + <> + + + ); +}; domReady( function () { const rootElement = document.querySelector( '.dashboard-content-area' ); const root = createRoot( rootElement! ); - root.render( - - ); + root.render( ); } ); diff --git a/src/Dashboard/tailwind.scss b/src/Dashboard/tailwind.scss index 0a10673143..09237f265a 100644 --- a/src/Dashboard/tailwind.scss +++ b/src/Dashboard/tailwind.scss @@ -1,2 +1,8 @@ @config './../../base-tailwind.config.js'; @import '../base-tailwind'; + +#headlessui-portal-root { + [role-dialog] { + z-index: 9999; + } +} diff --git a/tests/php/src/VendorNavMenuCheckerTest.php b/tests/php/src/VendorNavMenuCheckerTest.php new file mode 100644 index 0000000000..f54aa17b14 --- /dev/null +++ b/tests/php/src/VendorNavMenuCheckerTest.php @@ -0,0 +1,347 @@ +get( VendorNavMenuChecker::class ); + $this->assertInstanceOf( VendorNavMenuChecker::class, $service ); + } + + /** + * Test that template dependencies are returned. + * + * @test + */ + public function test_that_template_dependencies_are_returned() { + $checker = new VendorNavMenuChecker(); + $dependencies = $checker->get_template_dependencies(); + $this->assertIsArray( $dependencies ); + } + + /** + * Test that menu items are converted to react menu items. + * + * @test + */ + public function test_that_menu_items_are_converted_to_react_menu_items() { + $checker = new VendorNavMenuChecker(); + $menu_items = [ + 'dashboard' => [ + 'route' => 'dashboard', + 'url' => 'http://example.com/dashboard', + 'name' => 'Dashboard', + ], + 'products' => [ + 'route' => 'products', + 'url' => 'http://example.com/products', + 'name' => 'Products', + ], + 'orders' => [ + 'route' => 'orders', + 'url' => 'http://example.com/orders', + 'name' => 'Orders', + ], + ]; + $react_menu_items = $checker->convert_to_react_menu( $menu_items ); + $this->assertIsArray( $react_menu_items ); + $this->assertNotEquals( $menu_items['dashboard']['url'], $react_menu_items['dashboard']['url'] ); + $this->assertNotEquals( $menu_items['products']['url'], $react_menu_items['products']['url'] ); + + add_filter( + 'dokan_get_dashboard_nav_template_dependency', + function ( $template_dependencies ) { + $template_dependencies['dashboard'][] = [ + 'slug' => 'dashboard/dashboard', + 'name' => '', + 'args' => [], + ]; + $template_dependencies['dashboard'][] = [ + 'slug' => 'big-counter', + 'name' => 'widget', + 'args' => [], + ]; + $template_dependencies['dashboard'][] = [ + 'slug' => 'orders', + 'name' => 'widget', + 'args' => [], + ]; + $template_dependencies['dashboard'][] = [ + 'slug' => 'products', + 'name' => 'widget', + 'args' => [], + ]; + $template_dependencies['dashboard'][] = [ + 'slug' => 'sales-chart', + 'name' => 'widget', + 'args' => [], + ]; + return $template_dependencies; + } + ); + + $react_menu_items = $checker->convert_to_react_menu( $menu_items ); + $this->assertIsArray( $react_menu_items ); + $this->assertNotEquals( $menu_items['dashboard']['url'], $react_menu_items['dashboard']['url'] ); + $this->assertNotEquals( $menu_items['products']['url'], $react_menu_items['products']['url'] ); + } + + /** + * Test that menu items are not converted to react if template is overridden by file. + * + * @test + */ + public function test_that_menu_items_are_not_converted_to_react_if_template_is_overridden_by_file() { + $checker = new VendorNavMenuChecker(); + $menu_items = [ + 'dashboard' => [ + 'route' => 'dashboard', + 'url' => dokan_get_navigation_url(), + 'name' => 'Dashboard', + ], + 'products' => [ + 'route' => 'products', + 'url' => dokan_get_navigation_url( 'products' ), + 'name' => 'Products', + ], + 'orders' => [ + 'route' => 'orders', + 'url' => dokan_get_navigation_url( 'orders' ), + 'name' => 'Orders', + ], + ]; + + add_filter( + 'dokan_get_dashboard_nav_template_dependency', + function ( $template_dependencies ) { + $template_dependencies['dashboard'][] = [ + 'slug' => 'dashboard/dashboard', + 'name' => '', + 'args' => [], + ]; + $template_dependencies['dashboard'][] = [ + 'slug' => 'big-counter', + 'name' => 'widget', + 'args' => [], + ]; + $template_dependencies['dashboard'][] = [ + 'slug' => 'orders', + 'name' => 'widget', + 'args' => [], + ]; + $template_dependencies['dashboard'][] = [ + 'slug' => 'products', + 'name' => 'widget', + 'args' => [], + ]; + $template_dependencies['dashboard'][] = [ + 'slug' => 'sales-chart', + 'name' => 'widget', + 'args' => [], + ]; + + return $template_dependencies; + } + ); + + $react_menu_items = $checker->convert_to_react_menu( $menu_items ); + $this->assertIsArray( $react_menu_items ); + $this->assertNotEquals( $menu_items['dashboard']['url'], $react_menu_items['dashboard']['url'] ); + $this->assertNotEquals( $menu_items['products']['url'], $react_menu_items['products']['url'] ); + + $theme_dir = wp_get_theme()->get_stylesheet_directory(); + $file = $theme_dir . '/dokan/dashboard/dashboard.php'; + + self::touch( $file ); + + $react_menu_items = $checker->convert_to_react_menu( $menu_items ); + $this->assertIsArray( $react_menu_items ); + $this->assertEquals( $menu_items['dashboard']['url'], $react_menu_items['dashboard']['url'] ); + $this->assertNotEquals( $menu_items['products']['url'], $react_menu_items['products']['url'] ); + + self::unlink( $file ); + } + + /** + * Test that menu items are not converted to react if template is overridden by third party plugin. + * + * @test + */ + public function test_that_menu_items_are_not_converted_to_react_if_template_is_overridden_by_third_party_plugin() { + $checker = new VendorNavMenuChecker(); + $menu_items = [ + 'dashboard' => [ + 'route' => 'dashboard', + 'url' => dokan_get_navigation_url(), + 'name' => 'Dashboard', + ], + 'products' => [ + 'route' => 'products', + 'url' => dokan_get_navigation_url( 'products' ), + 'name' => 'Products', + ], + 'orders' => [ + 'route' => 'orders', + 'url' => dokan_get_navigation_url( 'orders' ), + 'name' => 'Orders', + ], + ]; + + add_filter( + 'dokan_get_dashboard_nav_template_dependency', + function ( $template_dependencies ) { + $template_dependencies['dashboard'][] = [ + 'slug' => 'dashboard/dashboard', + 'name' => '', + 'args' => [], + ]; + $template_dependencies['dashboard'][] = [ + 'slug' => 'big-counter', + 'name' => 'widget', + 'args' => [], + ]; + $template_dependencies['dashboard'][] = [ + 'slug' => 'orders', + 'name' => 'widget', + 'args' => [], + ]; + $template_dependencies['dashboard'][] = [ + 'slug' => 'products', + 'name' => 'widget', + 'args' => [], + ]; + $template_dependencies['dashboard'][] = [ + 'slug' => 'sales-chart', + 'name' => 'widget', + 'args' => [], + ]; + + return $template_dependencies; + } + ); + + $react_menu_items = $checker->convert_to_react_menu( $menu_items ); + $this->assertIsArray( $react_menu_items ); + $this->assertNotEquals( $menu_items['dashboard']['url'], $react_menu_items['dashboard']['url'] ); + $this->assertNotEquals( $menu_items['products']['url'], $react_menu_items['products']['url'] ); + + $custom_template_file = WP_PLUGIN_DIR . '/dokan-custom/dashboard/dashboard.php'; + + add_filter( + 'dokan_get_template_part', + function ( $template, $slug, $name ) use ( $custom_template_file ) { + if ( 'dashboard/dashboard' === $slug && '' === $name ) { + return $custom_template_file; + } + + return $template; + }, + 10, + 3 + ); + + $react_menu_items = $checker->convert_to_react_menu( $menu_items ); + $this->assertIsArray( $react_menu_items ); + $this->assertEquals( $menu_items['dashboard']['url'], $react_menu_items['dashboard']['url'] ); + $this->assertNotEquals( $menu_items['products']['url'], $react_menu_items['products']['url'] ); + } + + /** + * Test that menu items template dependency is being resolved on template override. + * + * @test + */ + public function test_that_menu_items_template_dependency_is_being_resolved_on_template_override() { + $checker = new VendorNavMenuChecker(); + $menu_items = [ + 'dashboard' => [ + 'route' => 'dashboard', + 'url' => dokan_get_navigation_url(), + 'name' => 'Dashboard', + ], + 'products' => [ + 'route' => 'products', + 'url' => dokan_get_navigation_url( 'products' ), + 'name' => 'Products', + ], + 'orders' => [ + 'route' => 'orders', + 'url' => dokan_get_navigation_url( 'orders' ), + 'name' => 'Orders', + ], + ]; + + add_filter( + 'dokan_get_dashboard_nav_template_dependency', + function ( $template_dependencies ) { + $template_dependencies['dashboard'][] = [ + 'slug' => 'dashboard/dashboard', + 'name' => '', + 'args' => [], + ]; + $template_dependencies['dashboard'][] = [ + 'slug' => 'dashboard/big-counter', + 'name' => 'widget', + 'args' => [], + ]; + $template_dependencies['dashboard'][] = [ + 'slug' => 'dashboard/orders', + 'name' => 'widget', + 'args' => [], + ]; + $template_dependencies['dashboard'][] = [ + 'slug' => 'dashboard/products', + 'name' => 'widget', + 'args' => [], + ]; + $template_dependencies['dashboard'][] = [ + 'slug' => 'dashboard/sales-chart', + 'name' => 'widget', + 'args' => [], + ]; + + return $template_dependencies; + } + ); + + $theme_dir = wp_get_theme()->get_stylesheet_directory(); + $products_widget_file = $theme_dir . '/dokan/dashboard/products-widget.php'; + $orders_widget_file = $theme_dir . '/dokan/dashboard/orders-widget.php'; + + self::touch( $products_widget_file ); + self::touch( $orders_widget_file ); + + $custom_template_file = WP_PLUGIN_DIR . '/dokan-custom/dashboard/dashboard.php'; + + add_filter( + 'dokan_get_template_part', + function ( $template, $slug, $name ) use ( $custom_template_file ) { + if ( 'dashboard/dashboard' === $slug && '' === $name ) { + return $custom_template_file; + } + + return $template; + }, + 10, + 3 + ); + + $overridden = $checker->list_overridden_templates(); + + self::unlink( $products_widget_file ); + self::unlink( $orders_widget_file ); + + $this->assertIsArray( $overridden ); + $this->assertContains( $products_widget_file, $overridden ); + $this->assertContains( $orders_widget_file, $overridden ); + $this->assertContains( $custom_template_file, $overridden ); + } +}