diff --git a/assets/css/admin/facebook-for-woocommerce-connection.css b/assets/css/admin/facebook-for-woocommerce-connection.css
index 03a1016b8..77013e9be 100644
--- a/assets/css/admin/facebook-for-woocommerce-connection.css
+++ b/assets/css/admin/facebook-for-woocommerce-connection.css
@@ -82,6 +82,14 @@
min-height: calc(100vh - 200px);
}
+#facebook-commerce-iframe-enhanced {
+ width: 100%;
+ max-width: 1100px;
+ min-height: calc(100vh - 200px);
+ background: transparent;
+ border: none;
+}
+
.woocommerce-embed-page #wpbody-content {
padding-bottom: 0;
-}
\ No newline at end of file
+}
diff --git a/class-wc-facebookcommerce.php b/class-wc-facebookcommerce.php
index bf457a206..b6952b172 100644
--- a/class-wc-facebookcommerce.php
+++ b/class-wc-facebookcommerce.php
@@ -60,6 +60,9 @@ class WC_Facebookcommerce extends WooCommerce\Facebook\Framework\Plugin {
/** @var WooCommerce\Facebook\Admin\Settings */
private $admin_settings;
+ /** @var WooCommerce\Facebook\Admin\Enhanced_Settings */
+ private $admin_enhanced_settings;
+
/** @var WooCommerce\Facebook\AJAX Ajax handler instance */
private $ajax;
@@ -234,7 +237,11 @@ public function init() {
// load admin handlers, before admin_init
if ( is_admin() ) {
- $this->admin_settings = new WooCommerce\Facebook\Admin\Settings( $this->connection_handler->is_connected() );
+ if ($this->get_integration()->use_enhanced_onboarding()) {
+ $this->admin_enhanced_settings = new WooCommerce\Facebook\Admin\Enhanced_Settings( $this->connection_handler->is_connected() );
+ } else {
+ $this->admin_settings = new WooCommerce\Facebook\Admin\Settings( $this->connection_handler->is_connected() );
+ }
}
}
}
diff --git a/includes/Admin/Abstract_Settings_Screen.php b/includes/Admin/Abstract_Settings_Screen.php
index 3b9765d6f..3ecca239f 100644
--- a/includes/Admin/Abstract_Settings_Screen.php
+++ b/includes/Admin/Abstract_Settings_Screen.php
@@ -129,9 +129,11 @@ protected function is_current_screen_page() {
return false;
}
// assume we are on the Connection tab by default because the link under Marketing doesn't include the tab query arg
- $connection_handler = facebook_for_woocommerce()->get_connection_handler();
- $default_tab = $connection_handler->is_connected() ? 'advertise' : 'connection';
- $tab = Helper::get_requested_value( 'tab', $default_tab );
+ $connection_handler = facebook_for_woocommerce()->get_connection_handler();
+ $use_enhanced_onboarding = facebook_for_woocommerce()->get_integration()->use_enhanced_onboarding();
+ $default_tab = $use_enhanced_onboarding ? 'shops' : ( $connection_handler->is_connected() ? 'advertise' : 'connection' );
+ $tab = Helper::get_requested_value( 'tab', $default_tab );
+
return ! empty( $tab ) && $tab === $this->get_id();
}
diff --git a/includes/Admin/Enhanced_Settings.php b/includes/Admin/Enhanced_Settings.php
new file mode 100644
index 000000000..0f0b19c44
--- /dev/null
+++ b/includes/Admin/Enhanced_Settings.php
@@ -0,0 +1,406 @@
+screens = $this->build_menu_item_array( $is_connected );
+
+ add_action( 'admin_menu', array( $this, 'add_menu_item' ) );
+ add_action( 'wp_loaded', array( $this, 'save' ) );
+
+ // TODO: Remove these hookds once catalog changes are complete
+ add_filter( 'parent_file', array( $this, 'set_parent_and_submenu_file' ) );
+ add_action( 'all_admin_notices', array( $this, 'add_tabs_to_product_sets_taxonomy' ) );
+ }
+
+ /**
+ * Arranges the tabs.
+ *
+ * @since 3.5.0
+ *
+ * @param bool $is_connected is Facebook connected
+ * @return array
+ */
+ private function build_menu_item_array( bool $is_connected ): array {
+ if ( $is_connected ) {
+ // TODO: Add Utility messaging tab
+ // TODO: Remove Product sync and Product sets tab once catalog changes are complete
+ return array(
+ Settings_Screens\Shops::ID => new Settings_Screens\Shops(),
+ Settings_Screens\Advertise::ID => new Settings_Screens\Advertise(),
+ Settings_Screens\Product_Sync::ID => new Settings_Screens\Product_Sync(),
+ Settings_Screens\Product_Sets::ID => new Settings_Screens\Product_Sets(),
+ );
+ } else {
+ // TODO: Add Utility messaging tab
+ return [ Settings_Screens\Shops::ID => new Settings_Screens\Shops() ];
+ }
+ }
+
+ /**
+ * Adds the Facebook menu item.
+ *
+ * @since 3.5.0
+ */
+ public function add_menu_item() {
+ $root_menu_item = $this->root_menu_item();
+
+ add_submenu_page(
+ $root_menu_item,
+ __( 'Facebook for WooCommerce', 'facebook-for-woocommerce' ),
+ __( 'Facebook', 'facebook-for-woocommerce' ),
+ 'manage_woocommerce',
+ self::PAGE_ID,
+ [ $this, 'render' ],
+ 5
+ );
+
+ $this->connect_to_enhanced_admin( $this->is_marketing_enabled() ? 'marketing_page_wc-facebook' : 'woocommerce_page_wc-facebook' );
+ }
+
+ /**
+ * Gets the root menu item.
+ *
+ * @since 3.5.0
+ *
+ * @return string
+ */
+ public function root_menu_item() {
+ if ( $this->is_marketing_enabled() ) {
+ return 'woocommerce-marketing';
+ }
+
+ return 'woocommerce';
+ }
+
+ /**
+ * Checks if marketing feature is enabled.
+ *
+ * @since 3.5.0
+ *
+ * @return bool
+ */
+ public function is_marketing_enabled() {
+ if ( class_exists( WooAdminFeatures::class ) ) {
+ return WooAdminFeatures::is_enabled( 'marketing' );
+ }
+
+ return is_callable( '\Automattic\WooCommerce\Admin\Loader::is_feature_enabled' )
+ && \Automattic\WooCommerce\Admin\Loader::is_feature_enabled( 'marketing' );
+ }
+
+ /**
+ * Enables enhanced admin support for the main Facebook settings page.
+ *
+ * @since 3.5.0
+ *
+ * @param string $screen_id
+ */
+ private function connect_to_enhanced_admin( $screen_id ) {
+ if ( is_callable( 'wc_admin_connect_page' ) ) {
+ $crumbs = array(
+ __( 'Facebook for WooCommerce', 'facebook-for-woocommerce' ),
+ );
+ //phpcs:ignore WordPress.Security.NonceVerification.Recommended
+ if ( ! empty( $_GET['tab'] ) ) {
+ //phpcs:ignore WordPress.Security.NonceVerification.Recommended
+ switch ( $_GET['tab'] ) {
+ case Shops::ID:
+ $crumbs[] = __( 'Shops', 'facebook-for-woocommerce' );
+ break;
+ case Settings_Screens\Product_Sync::ID:
+ $crumbs[] = __( 'Product sync', 'facebook-for-woocommerce' );
+ break;
+ case Settings_Screens\Advertise::ID:
+ $crumbs[] = __( 'Advertise', 'facebook-for-woocommerce' );
+ break;
+ }
+ }
+ wc_admin_connect_page(
+ array(
+ 'id' => self::PAGE_ID,
+ 'screen_id' => $screen_id,
+ 'path' => add_query_arg( 'page', self::PAGE_ID, 'admin.php' ),
+ 'title' => $crumbs,
+ )
+ );
+ }
+ }
+
+ /**
+ * Renders the settings page.
+ *
+ * @since 3.5.0
+ */
+ public function render() {
+ $current_tab = $this->get_current_tab();
+ $screen = $this->get_screen( $current_tab );
+
+ ?>
+
+ render_tabs( $current_tab ); ?>
+ get_message_handler()->show_messages(); ?>
+
+
get_title() ); ?>
+
get_description() ); ?>
+ render(); ?>
+
+
+ get_tabs();
+
+ ?>
+
+ get_tabs();
+ $current_tab = Helper::get_requested_value( 'tab' );
+
+ if ( ! $current_tab ) {
+ $current_tab = current( array_keys( $tabs ) );
+ }
+
+ return $current_tab;
+ }
+
+ /**
+ * Saves the settings page.
+ *
+ * @since 3.5.0
+ */
+ public function save() {
+ if ( ! is_admin() || Helper::get_requested_value( 'page' ) !== self::PAGE_ID ) {
+ return;
+ }
+
+ $screen = $this->get_screen( Helper::get_posted_value( 'screen_id' ) );
+ if ( ! $screen ) {
+ return;
+ }
+
+ if ( ! Helper::get_posted_value( 'save_' . $screen->get_id() . '_settings' ) ) {
+ return;
+ }
+
+ if ( ! current_user_can( 'manage_woocommerce' ) ) {
+ wp_die( esc_html__( 'You do not have permission to save these settings.', 'facebook-for-woocommerce' ) );
+ }
+
+ check_admin_referer( 'wc_facebook_admin_save_' . $screen->get_id() . '_settings' );
+ try {
+ $screen->save();
+ facebook_for_woocommerce()->get_message_handler()->add_message( __( 'Your settings have been saved.', 'facebook-for-woocommerce' ) );
+ } catch ( PluginException $exception ) {
+ facebook_for_woocommerce()->get_message_handler()->add_error(
+ sprintf(
+ /* translators: Placeholders: %s - user-friendly error message */
+ __( 'Your settings could not be saved. %s', 'facebook-for-woocommerce' ),
+ $exception->getMessage()
+ )
+ );
+ }
+ }
+
+ /**
+ * Gets a settings screen object based on ID.
+ *
+ * @since 3.5.0
+ *
+ * @param string $screen_id
+ * @return Abstract_Settings_Screen | null
+ */
+ public function get_screen( $screen_id ) {
+ $screens = $this->get_screens();
+
+ return ! empty( $screens[ $screen_id ] ) && $screens[ $screen_id ] instanceof Abstract_Settings_Screen ? $screens[ $screen_id ] : null;
+ }
+
+ /**
+ * Gets the available screens.
+ *
+ * @since 3.5.0
+ *
+ * @return Abstract_Settings_Screen[]
+ */
+ public function get_screens() {
+ /**
+ * Filters the admin settings screens.
+ *
+ * @since 3.5.0
+ *
+ * @param array $screens
+ */
+ $screens = (array) apply_filters( 'wc_facebook_admin_settings_screens', $this->screens, $this );
+
+ $screens = array_filter(
+ $screens,
+ function ( $value ) {
+ return $value instanceof Abstract_Settings_Screen;
+ }
+ );
+
+ return $screens;
+ }
+
+ /**
+ * Gets the tabs.
+ *
+ * @since 3.5.0
+ *
+ * @return array
+ */
+ public function get_tabs() {
+ $tabs = [];
+
+ foreach ( $this->get_screens() as $screen_id => $screen ) {
+ $tabs[ $screen_id ] = $screen->get_label();
+ }
+
+ /**
+ * Filters the admin settings tabs.
+ *
+ * @since 3.5.0
+ *
+ * @param array $tabs
+ */
+ return (array) apply_filters( 'wc_facebook_admin_settings_tabs', $tabs, $this );
+ }
+
+ /**
+ * TODO: Remove this function once catalog changes are complete
+ *
+ * Set the parent and submenu file while accessing Facebook Product Sets in the marketing menu.
+ *
+ * @since 3.5.0
+ *
+ * @param string $parent_file
+ * @return string
+ */
+ public function set_parent_and_submenu_file( $parent_file ) {
+ global $pagenow, $submenu_file;
+
+ $root_menu_item = $this->root_menu_item();
+
+ if ( 'edit-tags.php' === $pagenow || 'term.php' === $pagenow ) {
+ if ( isset( $_GET['taxonomy'] ) && 'fb_product_set' === $_GET['taxonomy'] ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended
+ $parent_file = $root_menu_item;
+ $submenu_file = self::PAGE_ID; //phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
+ }
+ }
+
+ return $parent_file;
+ }
+
+ /**
+ * TODO: Remove this function once catalog changes are complete
+ *
+ * Add the Facebook for WooCommerce tabs to the Facebook Product Set taxonomy page.
+ * Renders the tabs (hidden by default) at the stop of the page,
+ * then moves them to the correct DOM location with JavaScript and displays them.
+ *
+ * @since 3.3.0
+ */
+ public function add_tabs_to_product_sets_taxonomy() {
+
+ // Only load this on the edit-tags.php page
+ $screen = get_current_screen();
+ $is_taxonomy_list_page = 'edit-tags' === $screen->base;
+ $is_taxonomy_term_page = 'term' === $screen->base;
+ $is_taxonomy_page = $is_taxonomy_list_page || $is_taxonomy_term_page;
+ $is_product_set_taxonomy = 'fb_product_set' === $screen->taxonomy && $is_taxonomy_page;
+
+ if ( $is_product_set_taxonomy ) {
+ $this->render_tabs( Settings_Screens\Product_Sets::ID );
+ ?>
+
+ is_current_screen_page() ) {
+ wp_enqueue_script( 'wp-api' );
+ }
+ }
+
+ /**
+ * Initializes this settings page's properties.
+ *
+ * @since 3.5.0
+ */
+ public function initHook(): void {
+ $this->id = self::ID;
+ $this->label = __( 'Shops', 'facebook-for-woocommerce' );
+ $this->title = __( 'Shops', 'facebook-for-woocommerce' );
+ }
+
+ /**
+ * Adds admin notices.
+ *
+ * @since 3.5.0
+ *
+ * @internal
+ */
+ public function add_notices() {
+ if ( get_transient( 'wc_facebook_connection_failed' ) ) {
+ $message = sprintf(
+ /* translators: Placeholders: %1$s - tag, %2$s - tag, %3$s - tag, %4$s - tag, %5$s - tag, %6$s - tag */
+ __( '%1$sHeads up!%2$s It looks like there was a problem with reconnecting your site to Facebook. Please %3$sclick here%4$s to try again, or %5$sget in touch with our support team%6$s for assistance.', 'facebook-for-woocommerce' ),
+ '',
+ '',
+ '',
+ '',
+ '',
+ ''
+ );
+
+ facebook_for_woocommerce()->get_admin_notice_handler()->add_admin_notice(
+ $message,
+ 'wc_facebook_connection_failed',
+ array(
+ 'notice_class' => 'error',
+ )
+ );
+
+ delete_transient( 'wc_facebook_connection_failed' );
+ }
+ }
+
+
+ /**
+ * Enqueues the assets.
+ *
+ * @since 3.5.0
+ *
+ * @internal
+ */
+ public function enqueue_assets() {
+ if ( ! $this->is_current_screen_page() ) {
+ return;
+ }
+
+ wp_enqueue_style( 'wc-facebook-admin-connection-settings', facebook_for_woocommerce()->get_plugin_url() . '/assets/css/admin/facebook-for-woocommerce-connection.css', array(), \WC_Facebookcommerce::VERSION );
+ }
+
+
+ /**
+ * Renders the screen.
+ *
+ * @since 3.5.0
+ */
+ public function render() {
+ $this->render_facebook_iframe();
+ }
+
+ /**
+ * Renders the appropriate Facebook iframe based on connection status.
+ *
+ * @since 3.5.0
+ */
+ private function render_facebook_iframe() {
+ $connection = facebook_for_woocommerce()->get_connection_handler();
+ $is_connected = $connection->is_connected();
+ $merchant_access_token = get_option( 'wc_facebook_merchant_access_token', '' );
+
+ if ( ! empty( $merchant_access_token ) && $is_connected ) {
+ $iframe_url = \WooCommerce\Facebook\Handlers\MetaExtension::generate_iframe_management_url(
+ $connection->get_external_business_id()
+ );
+ } else {
+ $iframe_url = \WooCommerce\Facebook\Handlers\MetaExtension::generate_iframe_splash_url(
+ $is_connected,
+ $connection->get_plugin(),
+ $connection->get_external_business_id()
+ );
+ }
+
+ if ( empty( $iframe_url ) ) {
+ return;
+ }
+
+ ?>
+
+
+
+ is_current_screen_page() ) {
+ return;
+ }
+
+ wp_add_inline_script( 'plugin-api-client', $this->generate_inline_enhanced_onboarding_script(), 'after' );
+ }
+
+ /**
+ * Generates the inline script for the enhanced onboarding flow.
+ *
+ * @since 3.5.0
+ *
+ * @return string
+ */
+ public function generate_inline_enhanced_onboarding_script() {
+ // Generate a fresh nonce for this request
+ $nonce = wp_json_encode( wp_create_nonce( 'wp_rest' ) );
+
+ // Create the inline script with HEREDOC syntax for better JS readability
+ return << f.feature_type === 'fb_shop')?.connected_assets?.commerce_merchant_settings_id || '',
+ ad_account_id: message.installed_features.find(f => f.feature_type === 'ads')?.connected_assets?.ad_account_id || '',
+ commerce_partner_integration_id: message.commerce_partner_integration_id || '',
+ profiles: message.profiles,
+ installed_features: message.installed_features
+ };
+
+ fbAPI.updateSettings(requestBody)
+ .then(function(response) {
+ if (response.success) {
+ window.location.reload();
+ } else {
+ console.error('Error updating Facebook settings:', response);
+ }
+ })
+ .catch(function(error) {
+ console.error('Error during settings update:', error);
+ });
+ }
+
+ if (messageEvent === 'CommerceExtension::RESIZE') {
+ const iframe = document.getElementById('facebook-commerce-iframe-enhanced');
+ if (iframe && message.height) {
+ iframe.height = message.height;
+ }
+ }
+
+ if (messageEvent === 'CommerceExtension::UNINSTALL') {
+ fbAPI.uninstallSettings()
+ .then(function(response) {
+ if (response.success) {
+ window.location.reload();
+ }
+ })
+ .catch(function(error) {
+ console.error('Error during uninstall:', error);
+ window.location.reload();
+ });
+ }
+ });
+ JAVASCRIPT;
+ }
+
+
+ /**
+ * Gets the screen settings.
+ *
+ * @since 3.5.0
+ *
+ * @return array
+ */
+ public function get_settings() {
+
+ return array(
+
+ array(
+ 'title' => __( 'Debug', 'facebook-for-woocommerce' ),
+ 'type' => 'title',
+ ),
+
+ array(
+ 'id' => \WC_Facebookcommerce_Integration::SETTING_ENABLE_DEBUG_MODE,
+ 'title' => __( 'Enable debug mode', 'facebook-for-woocommerce' ),
+ 'type' => 'checkbox',
+ 'desc' => __( 'Log plugin events for debugging.', 'facebook-for-woocommerce' ),
+ /* translators: %s URL to the documentation page. */
+ 'desc_tip' => sprintf( __( 'Only enable this if you are experiencing problems with the plugin. Learn more.', 'facebook-for-woocommerce' ), 'https://woocommerce.com/document/facebook-for-woocommerce/#debug-tools' ),
+ 'default' => 'no',
+ ),
+
+ array(
+ 'id' => \WC_Facebookcommerce_Integration::SETTING_ENABLE_NEW_STYLE_FEED_GENERATOR,
+ 'title' => __( 'Experimental! Enable new style feed generation', 'facebook-for-woocommerce' ),
+ 'type' => 'checkbox',
+ 'desc' => __( 'Use new, memory improved, feed generation process.', 'facebook-for-woocommerce' ),
+ /* translators: %s URL to the documentation page. */
+ 'desc_tip' => sprintf( __( 'This is an experimental feature in testing phase. Only enable this if you are experiencing problems with feed generation. Learn more.', 'facebook-for-woocommerce' ), 'https://woocommerce.com/document/facebook-for-woocommerce/#feed-generation' ),
+ 'default' => 'no',
+ ),
+ array( 'type' => 'sectionend' ),
+ );
+ }
+}
diff --git a/tests/Unit/Admin/Settings/ShopsTest.php b/tests/Unit/Admin/Settings/ShopsTest.php
new file mode 100644
index 000000000..8b8be5f18
--- /dev/null
+++ b/tests/Unit/Admin/Settings/ShopsTest.php
@@ -0,0 +1,41 @@
+shops = new Shops();
+ }
+
+ /**
+ * Test that enqueue_assets enqueues the expected styles when on the page
+ */
+ public function testEnqueueAssetsWhenNotOnPage(): void {
+ // Mock is_current_screen_page to return false
+ $shops = $this->getMockBuilder(Shops::class)
+ ->onlyMethods(['is_current_screen_page'])
+ ->getMock();
+
+ $shops->method('is_current_screen_page')
+ ->willReturn(false);
+
+ // No styles should be enqueued
+ $shops->enqueue_assets();
+
+ $this->assertFalse(wp_style_is('wc-facebook-admin-connection-settings'));
+ }
+}
diff --git a/tests/Unit/Admin/Settings_Screens/ShopsTest.php b/tests/Unit/Admin/Settings_Screens/ShopsTest.php
new file mode 100644
index 000000000..5b92a080e
--- /dev/null
+++ b/tests/Unit/Admin/Settings_Screens/ShopsTest.php
@@ -0,0 +1,129 @@
+shops = new Shops();
+ }
+
+ /**
+ * Helper method to invoke private/protected methods
+ *
+ * @param object $object Object instance
+ * @param string $methodName Method name to call
+ * @param array $parameters Parameters to pass into method
+ *
+ * @return mixed Method return value
+ */
+ private function invoke_method($object, $methodName, array $parameters = []) {
+ $reflection = new \ReflectionClass(get_class($object));
+ $method = $reflection->getMethod($methodName);
+ $method->setAccessible(true);
+
+ return $method->invokeArgs($object, $parameters);
+ }
+
+ /**
+ * Test that render method calls render_facebook_iframe when enhanced onboarding is enabled
+ */
+ public function test_render_facebook_box_iframe() {
+ // Create a mock of the Shops class
+ $shops = $this->getMockBuilder(Shops::class)
+ ->getMock();
+
+ // Start output buffering to capture the render output
+ ob_start();
+ $shops->render();
+ $output = ob_get_clean();
+
+ // Since we can't directly test the private render_facebook_iframe method,
+ // we'll verify that the render method doesn't output the legacy Facebook box
+ // when enhanced onboarding is enabled
+ $this->assertStringNotContainsString('wc-facebook-shops-box', $output);
+ }
+
+ /**
+ * Test that render_message_handler outputs the expected JavaScript
+ */
+ public function test_render_message_handler() {
+ // Create a mock of the Shops class
+ $shops_mock = $this->getMockBuilder(Shops::class)
+ ->onlyMethods(['is_current_screen_page'])
+ ->getMock();
+
+ // Configure the mock to return true for is_current_screen_page
+ $shops_mock->method('is_current_screen_page')
+ ->willReturn(true);
+
+ // Call the method
+ $output = $shops_mock->generate_inline_enhanced_onboarding_script();
+
+ // Assert JavaScript event listeners and handlers
+ $this->assertStringContainsString('window.addEventListener(\'message\'', $output);
+ $this->assertStringContainsString('CommerceExtension::INSTALL', $output);
+ $this->assertStringContainsString('CommerceExtension::RESIZE', $output);
+ $this->assertStringContainsString('CommerceExtension::UNINSTALL', $output);
+
+ // Assert fetch request setup - check for wpApiSettings.root instead of hardcoded path
+ $this->assertStringContainsString('GeneratePluginAPIClient', $output);
+ $this->assertStringContainsString('fbAPI.updateSettings', $output);
+ }
+
+ /**
+ * Test that render_message_handler doesn't output when not on current screen
+ */
+ public function test_render_message_handler_not_current_screen() {
+ // Create a mock of the Shops class
+ $shops_mock = $this->getMockBuilder(Shops::class)
+ ->onlyMethods(['is_current_screen_page'])
+ ->getMock();
+
+ $shops_mock->method('is_current_screen_page')
+ ->willReturn(false);
+
+ // Start output buffering to capture the render output
+ ob_start();
+ $shops_mock->render_message_handler();
+ $output = ob_get_clean();
+
+ // Assert that no output is generated
+ $this->assertEmpty($output);
+ }
+
+ /**
+ * Test that the management URL is used when merchant token exists
+ */
+ public function test_renders_management_url_based_on_merchant_token() {
+ // Create a mock of the Shops class
+ $shops = $this->getMockBuilder(Shops::class)
+ ->getMock();
+
+ // Set up the merchant token
+ update_option('wc_facebook_merchant_access_token', 'test_token');
+
+ // Start output buffering to capture the render output
+ ob_start();
+ $this->invoke_method($shops, 'render_facebook_iframe');
+ $output = ob_get_clean();
+
+ // Check that the iframe is rendered
+ $this->assertStringContainsString('