diff --git a/.buildignore b/.buildignore index 6b201f42..0040e5e8 100644 --- a/.buildignore +++ b/.buildignore @@ -24,6 +24,7 @@ webpack.config.js .browserslistrc .eslintignore .eslintrc.js +.prettierrc .npmrc .stylelintrc diff --git a/.eslintrc.js b/.eslintrc.js index 0e995642..b7323d07 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,146 +1,44 @@ module.exports = { - extends: [ - 'plugin:react/recommended', - 'plugin:no-jquery/deprecated', - 'plugin:@wordpress/eslint-plugin/recommended-with-formatting', - ], - plugins: [ - 'babel', - 'react', - 'no-jquery', - ], - parser: '@babel/eslint-parser', - globals: { - wp: true, - window: true, - document: true, - _: false, - jQuery: false, - JSON: false, - elementorFrontend: true, - require: true, - elementor: true, - DialogsManager: true, - module: true, - React: true, - PropTypes: true, - __: true, - }, - parserOptions: { - ecmaVersion: 2017, - sourceType: 'module', - ecmaFeatures: { - jsx: true, - }, - }, - rules: { - // custom canceled rules - 'no-var': 'off', - 'vars-on-top': 'off', - 'wrap-iife': 'off', - 'computed-property-spacing': [ 'error', 'always' ], - 'comma-dangle': [ 'error', 'always-multiline' ], - 'no-undef': 'off', - 'no-unused-vars': [ 'warn', { ignoreRestSiblings: true } ], - 'dot-notation': 'error', - 'no-shadow': 'error', - 'no-lonely-if': 'error', - 'no-mixed-operators': 'error', - 'no-nested-ternary': 'error', - 'no-cond-assign': 'error', - 'space-in-parens': [ 'error', 'always', { exceptions: [ 'empty' ] } ], - 'no-multi-spaces': 'error', - 'semi-spacing': 'error', - 'quote-props': [ 'error', 'as-needed' ], - indent: [ 'off', 'tab', { SwitchCase: 1 } ], - 'no-mixed-spaces-and-tabs': 'error', - 'padded-blocks': [ 'error', 'never' ], - 'one-var-declaration-per-line': 'error', - 'no-extra-semi': 'error', - 'key-spacing': 'error', - 'array-bracket-spacing': [ 'error', 'always' ], - 'no-else-return': 'error', - 'no-console': 'warn', - //end of custom canceled rules - 'arrow-parens': [ 'error', 'always' ], - 'arrow-spacing': 'error', - 'brace-style': [ 'error', '1tbs' ], - camelcase: [ 'error', { properties: 'never' } ], - 'comma-spacing': 'error', - 'comma-style': 'error', - 'eol-last': 'error', - eqeqeq: 'error', - 'func-call-spacing': 'error', - 'jsx-quotes': 'error', - 'keyword-spacing': 'error', - 'lines-around-comment': 'off', - 'no-bitwise': [ 'error', { allow: [ '^' ] } ], - 'no-caller': 'error', - 'no-debugger': 'warn', - 'no-dupe-args': 'error', - 'no-dupe-keys': 'error', - 'no-duplicate-case': 'error', - 'no-eval': 'error', - 'no-multiple-empty-lines': [ 'error', { max: 1 } ], - 'no-multi-str': 'off', - 'no-negated-in-lhs': 'error', - 'no-redeclare': 'error', - 'no-restricted-syntax': [ - 'error', - { - selector: 'CallExpression[callee.name=/^__|_n|_x$/]:not([arguments.0.type=/^Literal|BinaryExpression$/])', - message: 'Translate function arguments must be string literals.', - }, - { - selector: 'CallExpression[callee.name=/^_n|_x$/]:not([arguments.1.type=/^Literal|BinaryExpression$/])', - message: 'Translate function arguments must be string literals.', - }, - { - selector: 'CallExpression[callee.name=_nx]:not([arguments.2.type=/^Literal|BinaryExpression$/])', - message: 'Translate function arguments must be string literals.', - }, - ], - 'no-trailing-spaces': 'error', - 'no-undef-init': 'error', - 'no-unreachable': 'error', - 'no-unsafe-negation': 'error', - 'no-unused-expressions': 'error', - 'no-useless-return': 'error', - 'no-whitespace-before-property': 'error', - 'object-curly-spacing': [ 'error', 'always' ], - 'prefer-const': 'warn', - quotes: [ 'error', 'single', { allowTemplateLiterals: true, avoidEscape: true } ], - semi: 'error', - 'space-before-blocks': [ 'error', 'always' ], - 'space-before-function-paren': [ 'error', { - anonymous: 'never', - named: 'never', - asyncArrow: 'always', - } ], - 'space-infix-ops': [ 'error', { int32Hint: false } ], - 'space-unary-ops': [ 'error', { - overrides: { - '!': true, - yield: true, - }, - } ], - 'valid-typeof': 'error', - yoda: [ 'error', 'always', { - onlyEquality: true, - } ], - 'react/react-in-jsx-scope': 'off', - 'babel/semi': 1, - // 'react/display-name': 'off', - // 'react/jsx-curly-spacing': [ 'error', { - // when: 'always', - // children: true, - // } ], - // 'react/jsx-equals-spacing': 'error', - // 'react/jsx-indent': [ 'error', 'tab' ], - // 'react/jsx-indent-props': [ 'error', 'tab' ], - // 'react/jsx-key': 'error', - // 'react/jsx-tag-spacing': 'error', - // 'react/no-children-prop': 'off', - // 'react/prop-types': 'off', - }, + extends: [ + 'plugin:react/recommended', + 'plugin:no-jquery/deprecated', + 'plugin:@wordpress/eslint-plugin/recommended-with-formatting', + 'plugin:prettier/recommended', + ], + plugins: ['babel', 'react', 'no-jquery'], + parser: '@babel/eslint-parser', + globals: { + wp: true, + window: true, + document: true, + _: false, + jQuery: false, + JSON: false, + elementorFrontend: true, + require: true, + elementor: true, + DialogsManager: true, + module: true, + React: true, + PropTypes: true, + __: true, + }, + parserOptions: { + ecmaVersion: 2017, + sourceType: 'module', + ecmaFeatures: { + jsx: true, + }, + }, + rules: { + 'no-undef': 'off', + 'no-unused-vars': 'off', + yoda: [ + 'error', + 'always', + { + onlyEquality: true, + }, + ], + }, }; diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 00000000..9da595c9 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,10 @@ +{ + "useTabs": true, + "printWidth": 80, + "semi": true, + "singleQuote": true, + "endOfLine": "auto", + "arrowParens": "always", + "proseWrap": "always", + "bracketSpacing": true +} diff --git a/dev/js/editor/component.js b/dev/js/editor/component.js index d9ef0d53..34c98c99 100644 --- a/dev/js/editor/component.js +++ b/dev/js/editor/component.js @@ -7,7 +7,6 @@ export default class extends $e.modules.ComponentBase { } defaultHooks() { - return this.importHooks( { ControlsHook } ); + return this.importHooks({ ControlsHook }); } } - diff --git a/dev/js/editor/hello-editor.js b/dev/js/editor/hello-editor.js index c9a4818e..a9468857 100644 --- a/dev/js/editor/hello-editor.js +++ b/dev/js/editor/hello-editor.js @@ -1,4 +1,3 @@ import HelloComponent from './component'; -$e.components.register( new HelloComponent() ); - +$e.components.register(new HelloComponent()); diff --git a/dev/js/editor/hooks/ui/controls-hook.js b/dev/js/editor/hooks/ui/controls-hook.js index b60aa949..dec5e608 100644 --- a/dev/js/editor/hooks/ui/controls-hook.js +++ b/dev/js/editor/hooks/ui/controls-hook.js @@ -25,141 +25,209 @@ export default class ControlsHook extends $e.modules.hookUI.After { return { hello_header_logo_display: { selector: '.site-header .site-logo, .site-header .site-title', - callback: ( $element, args ) => { - this.toggleShowHideClass( $element, args.settings.hello_header_logo_display ); + callback: ($element, args) => { + this.toggleShowHideClass( + $element, + args.settings.hello_header_logo_display, + ); }, }, hello_header_menu_display: { - selector: '.site-header .site-navigation, .site-header .site-navigation-toggle-holder', - callback: ( $element, args ) => { - this.toggleShowHideClass( $element, args.settings.hello_header_menu_display ); + selector: + '.site-header .site-navigation, .site-header .site-navigation-toggle-holder', + callback: ($element, args) => { + this.toggleShowHideClass( + $element, + args.settings.hello_header_menu_display, + ); }, }, hello_header_tagline_display: { selector: '.site-header .site-description', - callback: ( $element, args ) => { - this.toggleShowHideClass( $element, args.settings.hello_header_tagline_display ); + callback: ($element, args) => { + this.toggleShowHideClass( + $element, + args.settings.hello_header_tagline_display, + ); }, }, hello_header_logo_type: { selector: '.site-header .site-branding', - callback: ( $element, args ) => { + callback: ($element, args) => { const classPrefix = 'show-', - inputOptions = args.container.controls.hello_header_logo_type.options, + inputOptions = + args.container.controls.hello_header_logo_type.options, inputValue = args.settings.hello_header_logo_type; - this.toggleLayoutClass( $element, classPrefix, inputOptions, inputValue ); + this.toggleLayoutClass( + $element, + classPrefix, + inputOptions, + inputValue, + ); }, }, hello_header_layout: { selector: '.site-header', - callback: ( $element, args ) => { + callback: ($element, args) => { const classPrefix = 'header-', inputOptions = args.container.controls.hello_header_layout.options, inputValue = args.settings.hello_header_layout; - this.toggleLayoutClass( $element, classPrefix, inputOptions, inputValue ); + this.toggleLayoutClass( + $element, + classPrefix, + inputOptions, + inputValue, + ); }, }, hello_header_width: { selector: '.site-header', - callback: ( $element, args ) => { + callback: ($element, args) => { const classPrefix = 'header-', inputOptions = args.container.controls.hello_header_width.options, inputValue = args.settings.hello_header_width; - this.toggleLayoutClass( $element, classPrefix, inputOptions, inputValue ); + this.toggleLayoutClass( + $element, + classPrefix, + inputOptions, + inputValue, + ); }, }, hello_header_menu_layout: { selector: '.site-header', - callback: ( $element, args ) => { + callback: ($element, args) => { const classPrefix = 'menu-layout-', - inputOptions = args.container.controls.hello_header_menu_layout.options, + inputOptions = + args.container.controls.hello_header_menu_layout.options, inputValue = args.settings.hello_header_menu_layout; // No matter what, close the mobile menu - $element.find( '.site-navigation-toggle-holder' ).removeClass( 'elementor-active' ); - $element.find( '.site-navigation-dropdown' ).removeClass( 'show' ); + $element + .find('.site-navigation-toggle-holder') + .removeClass('elementor-active'); + $element.find('.site-navigation-dropdown').removeClass('show'); - this.toggleLayoutClass( $element, classPrefix, inputOptions, inputValue ); + this.toggleLayoutClass( + $element, + classPrefix, + inputOptions, + inputValue, + ); }, }, hello_header_menu_dropdown: { selector: '.site-header', - callback: ( $element, args ) => { + callback: ($element, args) => { const classPrefix = 'menu-dropdown-', - inputOptions = args.container.controls.hello_header_menu_dropdown.options, + inputOptions = + args.container.controls.hello_header_menu_dropdown.options, inputValue = args.settings.hello_header_menu_dropdown; - this.toggleLayoutClass( $element, classPrefix, inputOptions, inputValue ); + this.toggleLayoutClass( + $element, + classPrefix, + inputOptions, + inputValue, + ); }, }, hello_footer_logo_display: { selector: '.site-footer .site-logo, .site-footer .site-title', - callback: ( $element, args ) => { - this.toggleShowHideClass( $element, args.settings.hello_footer_logo_display ); + callback: ($element, args) => { + this.toggleShowHideClass( + $element, + args.settings.hello_footer_logo_display, + ); }, }, hello_footer_tagline_display: { selector: '.site-footer .site-description', - callback: ( $element, args ) => { - this.toggleShowHideClass( $element, args.settings.hello_footer_tagline_display ); + callback: ($element, args) => { + this.toggleShowHideClass( + $element, + args.settings.hello_footer_tagline_display, + ); }, }, hello_footer_menu_display: { selector: '.site-footer .site-navigation', - callback: ( $element, args ) => { - this.toggleShowHideClass( $element, args.settings.hello_footer_menu_display ); + callback: ($element, args) => { + this.toggleShowHideClass( + $element, + args.settings.hello_footer_menu_display, + ); }, }, hello_footer_copyright_display: { selector: '.site-footer .copyright', - callback: ( $element, args ) => { - const $footerContainer = $element.closest( '#site-footer' ), + callback: ($element, args) => { + const $footerContainer = $element.closest('#site-footer'), inputValue = args.settings.hello_footer_copyright_display; - this.toggleShowHideClass( $element, inputValue ); + this.toggleShowHideClass($element, inputValue); - $footerContainer.toggleClass( 'footer-has-copyright', 'yes' === inputValue ); + $footerContainer.toggleClass( + 'footer-has-copyright', + 'yes' === inputValue, + ); }, }, hello_footer_logo_type: { selector: '.site-footer .site-branding', - callback: ( $element, args ) => { + callback: ($element, args) => { const classPrefix = 'show-', - inputOptions = args.container.controls.hello_footer_logo_type.options, + inputOptions = + args.container.controls.hello_footer_logo_type.options, inputValue = args.settings.hello_footer_logo_type; - this.toggleLayoutClass( $element, classPrefix, inputOptions, inputValue ); + this.toggleLayoutClass( + $element, + classPrefix, + inputOptions, + inputValue, + ); }, }, hello_footer_layout: { selector: '.site-footer', - callback: ( $element, args ) => { + callback: ($element, args) => { const classPrefix = 'footer-', inputOptions = args.container.controls.hello_footer_layout.options, inputValue = args.settings.hello_footer_layout; - this.toggleLayoutClass( $element, classPrefix, inputOptions, inputValue ); + this.toggleLayoutClass( + $element, + classPrefix, + inputOptions, + inputValue, + ); }, }, hello_footer_width: { selector: '.site-footer', - callback: ( $element, args ) => { + callback: ($element, args) => { const classPrefix = 'footer-', inputOptions = args.container.controls.hello_footer_width.options, inputValue = args.settings.hello_footer_width; - this.toggleLayoutClass( $element, classPrefix, inputOptions, inputValue ); + this.toggleLayoutClass( + $element, + classPrefix, + inputOptions, + inputValue, + ); }, }, hello_footer_copyright_text: { selector: '.site-footer .copyright', - callback: ( $element, args ) => { + callback: ($element, args) => { const inputValue = args.settings.hello_footer_copyright_text; - $element.find( 'p' ).text( inputValue ); + $element.find('p').text(inputValue); }, }, }; @@ -173,8 +241,11 @@ export default class ControlsHook extends $e.modules.hookUI.After { * @param {jQuery} element * @param {string} inputValue */ - toggleShowHideClass( element, inputValue ) { - element.removeClass( 'hide' ).removeClass( 'show' ).addClass( inputValue ? 'show' : 'hide' ); + toggleShowHideClass(element, inputValue) { + element + .removeClass('hide') + .removeClass('show') + .addClass(inputValue ? 'show' : 'hide'); } /** @@ -187,15 +258,15 @@ export default class ControlsHook extends $e.modules.hookUI.After { * @param {Object} inputOptions * @param {string} inputValue */ - toggleLayoutClass( element, classPrefix, inputOptions, inputValue ) { + toggleLayoutClass(element, classPrefix, inputOptions, inputValue) { // Loop through the possible classes and remove the one that's not in use - Object.entries( inputOptions ).forEach( ( [ key ] ) => { - element.removeClass( classPrefix + key ); - } ); + Object.entries(inputOptions).forEach(([key]) => { + element.removeClass(classPrefix + key); + }); // Append the class which we want to use onto the element - if ( '' !== inputValue ) { - element.addClass( classPrefix + inputValue ); + if ('' !== inputValue) { + element.addClass(classPrefix + inputValue); } } @@ -204,20 +275,22 @@ export default class ControlsHook extends $e.modules.hookUI.After { * * @param {Object} args */ - getConditions( args ) { + getConditions(args) { const isKit = 'kit' === elementor.documents.getCurrent().config.type, - changedControls = Object.keys( args.settings ), + changedControls = Object.keys(args.settings), isSingleSetting = 1 === changedControls.length; // If the document is not a kit, or there are no changed settings, or there is more than one single changed // setting, don't run the hook. - if ( ! isKit || ! args.settings || ! isSingleSetting ) { + if (!isKit || !args.settings || !isSingleSetting) { return false; } // If the changed control is in the list of theme controls, return true to run the hook. // Otherwise, return false so the hook doesn't run. - return !! Object.keys( this.getHelloThemeControls() ).includes( changedControls[ 0 ] ); + return !!Object.keys(this.getHelloThemeControls()).includes( + changedControls[0], + ); } /** @@ -225,14 +298,14 @@ export default class ControlsHook extends $e.modules.hookUI.After { * * @param {Object} args */ - apply( args ) { + apply(args) { const allThemeControls = this.getHelloThemeControls(), // Extract the control ID from the passed args - controlId = Object.keys( args.settings )[ 0 ], - controlConfig = allThemeControls[ controlId ], + controlId = Object.keys(args.settings)[0], + controlConfig = allThemeControls[controlId], // Find the element that needs to be targeted by the control. - $element = elementor.$previewContents.find( controlConfig.selector ); + $element = elementor.$previewContents.find(controlConfig.selector); - controlConfig.callback( $element, args ); + controlConfig.callback($element, args); } } diff --git a/dev/js/frontend/hello-frontend.js b/dev/js/frontend/hello-frontend.js index 8807085e..35db2c4a 100644 --- a/dev/js/frontend/hello-frontend.js +++ b/dev/js/frontend/hello-frontend.js @@ -1,76 +1,101 @@ class elementorHelloThemeHandler { - constructor() { - this.initSettings(); - this.initElements(); - this.bindEvents(); - } + constructor() { + this.initSettings(); + this.initElements(); + this.bindEvents(); + } - initSettings() { - this.settings = { - selectors: { - menuToggle: '.site-header .site-navigation-toggle', - menuToggleHolder: '.site-header .site-navigation-toggle-holder', - dropdownMenu: '.site-header .site-navigation-dropdown', - }, - }; - } + initSettings() { + this.settings = { + selectors: { + menuToggle: '.site-header .site-navigation-toggle', + menuToggleHolder: '.site-header .site-navigation-toggle-holder', + dropdownMenu: '.site-header .site-navigation-dropdown', + }, + }; + } - initElements() { - this.elements = { - window, - menuToggle: document.querySelector( this.settings.selectors.menuToggle ), - menuToggleHolder: document.querySelector( this.settings.selectors.menuToggleHolder ), - dropdownMenu: document.querySelector( this.settings.selectors.dropdownMenu ), - }; - } + initElements() { + this.elements = { + window, + menuToggle: document.querySelector(this.settings.selectors.menuToggle), + menuToggleHolder: document.querySelector( + this.settings.selectors.menuToggleHolder, + ), + dropdownMenu: document.querySelector( + this.settings.selectors.dropdownMenu, + ), + }; + } - bindEvents() { - if ( ! this.elements.menuToggleHolder || this.elements.menuToggleHolder?.classList.contains( 'hide' ) ) { - return; - } + bindEvents() { + if ( + !this.elements.menuToggleHolder || + this.elements.menuToggleHolder?.classList.contains('hide') + ) { + return; + } - this.elements.menuToggle.addEventListener( 'click', () => this.handleMenuToggle() ); + this.elements.menuToggle.addEventListener('click', () => + this.handleMenuToggle(), + ); - this.elements.dropdownMenu.querySelectorAll( '.menu-item-has-children > a' ) - .forEach( ( anchorElement ) => anchorElement.addEventListener( 'click', ( event ) => this.handleMenuChildren( event ) ) ); - } + this.elements.dropdownMenu + .querySelectorAll('.menu-item-has-children > a') + .forEach((anchorElement) => + anchorElement.addEventListener('click', (event) => + this.handleMenuChildren(event), + ), + ); + } - closeMenuItems() { - this.elements.menuToggleHolder.classList.remove( 'elementor-active' ); - this.elements.window.removeEventListener( 'resize', () => this.closeMenuItems() ); - } + closeMenuItems() { + this.elements.menuToggleHolder.classList.remove('elementor-active'); + this.elements.window.removeEventListener('resize', () => + this.closeMenuItems(), + ); + } - handleMenuToggle() { - const isDropdownVisible = ! this.elements.menuToggleHolder.classList.contains( 'elementor-active' ); + handleMenuToggle() { + const isDropdownVisible = + !this.elements.menuToggleHolder.classList.contains('elementor-active'); - this.elements.menuToggle.setAttribute( 'aria-expanded', isDropdownVisible ); - this.elements.dropdownMenu.setAttribute( 'aria-hidden', ! isDropdownVisible ); - this.elements.dropdownMenu.inert = ! isDropdownVisible; - this.elements.menuToggleHolder.classList.toggle( 'elementor-active', isDropdownVisible ); + this.elements.menuToggle.setAttribute('aria-expanded', isDropdownVisible); + this.elements.dropdownMenu.setAttribute('aria-hidden', !isDropdownVisible); + this.elements.dropdownMenu.inert = !isDropdownVisible; + this.elements.menuToggleHolder.classList.toggle( + 'elementor-active', + isDropdownVisible, + ); - // Always close all sub active items. - this.elements.dropdownMenu.querySelectorAll( '.elementor-active' ).forEach( ( item ) => item.classList.remove( 'elementor-active' ) ); + // Always close all sub active items. + this.elements.dropdownMenu + .querySelectorAll('.elementor-active') + .forEach((item) => item.classList.remove('elementor-active')); - if ( isDropdownVisible ) { - this.elements.window.addEventListener( 'resize', () => this.closeMenuItems() ); - } else { - this.elements.window.removeEventListener( 'resize', () => this.closeMenuItems() ); - } - } + if (isDropdownVisible) { + this.elements.window.addEventListener('resize', () => + this.closeMenuItems(), + ); + } else { + this.elements.window.removeEventListener('resize', () => + this.closeMenuItems(), + ); + } + } - handleMenuChildren( event ) { - const anchor = event.currentTarget; - const parentLi = anchor.parentElement; + handleMenuChildren(event) { + const anchor = event.currentTarget; + const parentLi = anchor.parentElement; - if ( ! parentLi?.classList ) { - return; - } + if (!parentLi?.classList) { + return; + } - parentLi.classList.toggle( 'elementor-active' ); - } + parentLi.classList.toggle('elementor-active'); + } } -document.addEventListener( 'DOMContentLoaded', () => { - new elementorHelloThemeHandler(); -} ); - +document.addEventListener('DOMContentLoaded', () => { + new elementorHelloThemeHandler(); +}); diff --git a/modules/admin-home/assets/js/components/dynamic-icon.js b/modules/admin-home/assets/js/components/dynamic-icon.js index 3e1b1378..91c6569a 100644 --- a/modules/admin-home/assets/js/components/dynamic-icon.js +++ b/modules/admin-home/assets/js/components/dynamic-icon.js @@ -1,41 +1,41 @@ import React, { useState, useEffect } from 'react'; const componentMap = { - BrandYoutubeIcon: () => import( '../icons/youtube.tsx' ), - BrandElementorIcon: () => import( '../icons/elementor.tsx' ), - ThemeBuilderIcon: () => import( '@elementor/icons/ThemeBuilderIcon' ), - SettingsIcon: () => import( '@elementor/icons/SettingsIcon' ), - BrandFacebookIcon: () => import( '@elementor/icons/BrandFacebookIcon' ), - StarIcon: () => import( '@elementor/icons/StarIcon' ), - HelpIcon: () => import( '@elementor/icons/HelpIcon' ), - SpeakerphoneIcon: () => import( '@elementor/icons/SpeakerphoneIcon' ), - TextIcon: () => import( '@elementor/icons/TextIcon' ), - PhotoIcon: () => import( '@elementor/icons/PhotoIcon' ), - AppsIcon: () => import( '@elementor/icons/AppsIcon' ), - BrushIcon: () => import( '@elementor/icons/BrushIcon' ), - UnderlineIcon: () => import( '@elementor/icons/UnderlineIcon' ), - PagesIcon: () => import( '@elementor/icons/PagesIcon' ), - PageTypeIcon: () => import( '@elementor/icons/PageTypeIcon' ), - HeaderTemplateIcon: () => import( '@elementor/icons/HeaderTemplateIcon' ), - FooterTemplateIcon: () => import( '@elementor/icons/FooterTemplateIcon' ), + BrandYoutubeIcon: () => import('../icons/youtube.tsx'), + BrandElementorIcon: () => import('../icons/elementor.tsx'), + ThemeBuilderIcon: () => import('@elementor/icons/ThemeBuilderIcon'), + SettingsIcon: () => import('@elementor/icons/SettingsIcon'), + BrandFacebookIcon: () => import('@elementor/icons/BrandFacebookIcon'), + StarIcon: () => import('@elementor/icons/StarIcon'), + HelpIcon: () => import('@elementor/icons/HelpIcon'), + SpeakerphoneIcon: () => import('@elementor/icons/SpeakerphoneIcon'), + TextIcon: () => import('@elementor/icons/TextIcon'), + PhotoIcon: () => import('@elementor/icons/PhotoIcon'), + AppsIcon: () => import('@elementor/icons/AppsIcon'), + BrushIcon: () => import('@elementor/icons/BrushIcon'), + UnderlineIcon: () => import('@elementor/icons/UnderlineIcon'), + PagesIcon: () => import('@elementor/icons/PagesIcon'), + PageTypeIcon: () => import('@elementor/icons/PageTypeIcon'), + HeaderTemplateIcon: () => import('@elementor/icons/HeaderTemplateIcon'), + FooterTemplateIcon: () => import('@elementor/icons/FooterTemplateIcon'), }; -const DynamicIcon = ( { componentName, ...rest } ) => { - const [ Component, setComponent ] = useState( null ); +const DynamicIcon = ({ componentName, ...rest }) => { + const [Component, setComponent] = useState(null); - useEffect( () => { - if ( componentMap[ componentName ] ) { - componentMap[ componentName ]().then( ( module ) => { - setComponent( () => module.default ); - } ); + useEffect(() => { + if (componentMap[componentName]) { + componentMap[componentName]().then((module) => { + setComponent(() => module.default); + }); } - }, [ componentName ] ); + }, [componentName]); - if ( ! Component ) { - return null; - } + if (!Component) { + return null; + } - return ; + return ; }; export default DynamicIcon; diff --git a/modules/admin-home/assets/js/components/link/link-or-title.js b/modules/admin-home/assets/js/components/link/link-or-title.js index 6ddc379a..7c2e929c 100644 --- a/modules/admin-home/assets/js/components/link/link-or-title.js +++ b/modules/admin-home/assets/js/components/link/link-or-title.js @@ -3,25 +3,25 @@ import Typography from '@elementor/ui/Typography'; import Link from '@elementor/ui/Link'; import { decode } from 'html-entities'; -const LinkOrTitle = ( { title, link, sublinks, onClick, target } ) => ( +const LinkOrTitle = ({ title, link, sublinks, onClick, target }) => ( - { link && 0 === sublinks.length ? ( + {link && 0 === sublinks.length ? ( - { decode( title ) } + {decode(title)} ) : ( - - { decode( title ) } + + {decode(title)} - ) } + )} ); diff --git a/modules/admin-home/assets/js/components/link/link-with-icon-and-title.js b/modules/admin-home/assets/js/components/link/link-with-icon-and-title.js index 3b032578..9443a441 100644 --- a/modules/admin-home/assets/js/components/link/link-with-icon-and-title.js +++ b/modules/admin-home/assets/js/components/link/link-with-icon-and-title.js @@ -3,34 +3,34 @@ import SublinksList from './sub-links-list'; import LinkOrTitle from './link-or-title'; import DynamicIcon from '../dynamic-icon'; -export const LinkWithIconAndTitle = ( { +export const LinkWithIconAndTitle = ({ title, link = null, icon = 'SettingsIcon', sublinks = [], onClick = () => {}, target = '', -} ) => { +}) => { return ( - + - { sublinks.length > 0 && ( - - ) } + {sublinks.length > 0 && ( + + )} ); diff --git a/modules/admin-home/assets/js/components/link/promotion-link.js b/modules/admin-home/assets/js/components/link/promotion-link.js index 3d8fb6c3..fc8c8f62 100644 --- a/modules/admin-home/assets/js/components/link/promotion-link.js +++ b/modules/admin-home/assets/js/components/link/promotion-link.js @@ -6,59 +6,82 @@ import Image from '@elementor/ui/Image'; import { Feature } from '../promotions/feature'; import UpgradeIcon from '@elementor/icons/UpgradeIcon'; -export const PromotionLink = ( - { - image, - alt, - title, - messages, - button, - url, - features, - target = '_blank', - width = 100, - height = 100, - horizontalLayout = false, - upgrade = false, - backgroundImage = false, - backgroundColor = false, - buttonBgColor = false, - } ) => { +export const PromotionLink = ({ + image, + alt, + title, + messages, + button, + url, + features, + target = '_blank', + width = 100, + height = 100, + horizontalLayout = false, + upgrade = false, + backgroundImage = false, + backgroundColor = false, + buttonBgColor = false, +}) => { const backgroundFallback = backgroundImage ? 'transparent' : null; const paperSx = horizontalLayout - ? { display: 'flex', alignItems: 'center', justifyContent: 'space-between', p: 3, gap: 4, maxWidth: 600 } + ? { + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', + p: 3, + gap: 4, + maxWidth: 600, + } : { p: 3 }; - paperSx.backgroundImage = backgroundImage ? `url(${ backgroundImage })` : null; - paperSx.backgroundColor = backgroundColor ? backgroundColor : backgroundFallback; - paperSx.color = ( backgroundImage || backgroundColor ) ? 'rgb(12, 13, 14)' : null; + paperSx.backgroundImage = backgroundImage ? `url(${backgroundImage})` : null; + paperSx.backgroundColor = backgroundColor + ? backgroundColor + : backgroundFallback; + paperSx.color = backgroundImage || backgroundColor ? 'rgb(12, 13, 14)' : null; const stackSx = horizontalLayout ? { flex: 0.6, alignItems: 'center', justifyContent: 'center' } : { alignItems: 'center', justifyContent: 'center' }; - const featuresStackSx = horizontalLayout - ? { flex: 0.4, mt: 4 } - : { mt: 4 }; + const featuresStackSx = horizontalLayout ? { flex: 0.4, mt: 4 } : { mt: 4 }; const startIcon = upgrade ? : null; return ( - - - { - { title } - { messages.map( ( message, i ) => { - return { message }; - } ) } - + + + {alt} + + {title} + + {messages.map((message, i) => { + return ( + + {message} + + ); + })} + - { features && ( - - { features.map( ( feature, i ) => { - return ; - } ) } - ) } + {features && ( + + {features.map((feature, i) => { + return ; + })} + + )} ); }; diff --git a/modules/admin-home/assets/js/components/link/sub-links-list.js b/modules/admin-home/assets/js/components/link/sub-links-list.js index 1a55ba2b..c0f4bc38 100644 --- a/modules/admin-home/assets/js/components/link/sub-links-list.js +++ b/modules/admin-home/assets/js/components/link/sub-links-list.js @@ -3,23 +3,23 @@ import Typography from '@elementor/ui/Typography'; import Link from '@elementor/ui/Link'; import { decode } from 'html-entities'; -const SublinksList = ( { sublinks, target } ) => ( - +const SublinksList = ({ sublinks, target }) => ( + - { sublinks.map( ( sublink, index ) => ( - - { index > 0 && | } + {sublinks.map((sublink, index) => ( + + {index > 0 && |} - { decode( sublink.title ) } + href={sublink.link} + target={sublink.target || target} + sx={{ lineHeight: 'initial', fontWeight: 'normal' }} + > + {decode(sublink.title)} - ) ) } + ))} ); diff --git a/modules/admin-home/assets/js/components/linkGroup/column-link-group.js b/modules/admin-home/assets/js/components/linkGroup/column-link-group.js index 61cfe3f9..77713e36 100644 --- a/modules/admin-home/assets/js/components/linkGroup/column-link-group.js +++ b/modules/admin-home/assets/js/components/linkGroup/column-link-group.js @@ -2,19 +2,26 @@ import Stack from '@elementor/ui/Stack'; import { LinkWithIconAndTitle } from '../link/link-with-icon-and-title'; import Typography from '@elementor/ui/Typography'; -export const ColumnLinkGroup = ( { links = [], title = '', noLinksMessage, sx = {} } ) => { - if ( ! links.length ) { +export const ColumnLinkGroup = ({ + links = [], + title = '', + noLinksMessage, + sx = {}, +}) => { + if (!links.length) { return null; } return ( - - { title && ( { title } ) } - { links.map( ( link ) => { - return ( ); - } ) } + + {title && {title}} + {links.map((link) => { + return ; + })} - { ! links.length && noLinksMessage && ( { noLinksMessage } ) } + {!links.length && noLinksMessage && ( + {noLinksMessage} + )} ); }; diff --git a/modules/admin-home/assets/js/components/paper/base-admin-paper.js b/modules/admin-home/assets/js/components/paper/base-admin-paper.js index 060ae2cd..3c19964f 100644 --- a/modules/admin-home/assets/js/components/paper/base-admin-paper.js +++ b/modules/admin-home/assets/js/components/paper/base-admin-paper.js @@ -1,9 +1,9 @@ import Paper from '@elementor/ui/Paper'; -export const BaseAdminPaper = ( { children, sx = { px: 4, py: 3 } } ) => { +export const BaseAdminPaper = ({ children, sx = { px: 4, py: 3 } }) => { return ( - - { children } + + {children} ); }; diff --git a/modules/admin-home/assets/js/components/paper/quick-links.js b/modules/admin-home/assets/js/components/paper/quick-links.js index a1dc6b23..1e0268bc 100644 --- a/modules/admin-home/assets/js/components/paper/quick-links.js +++ b/modules/admin-home/assets/js/components/paper/quick-links.js @@ -10,17 +10,19 @@ export const QuickLinks = () => { return ( - { __( 'Quick Links', 'hello-elementor' ) } - - { __( 'These quick actions will get your site airborne in a flash.', 'hello-elementor' ) } + + {__('Quick Links', 'hello-elementor')} - - { Object.keys( quickLinks ).map( ( key ) => { - return ( - - ); - } ) } - + + {__( + 'These quick actions will get your site airborne in a flash.', + 'hello-elementor', + )} + + + {Object.keys(quickLinks).map((key) => { + return ; + })} ); diff --git a/modules/admin-home/assets/js/components/paper/resources.js b/modules/admin-home/assets/js/components/paper/resources.js index 167a01fd..fa77887c 100644 --- a/modules/admin-home/assets/js/components/paper/resources.js +++ b/modules/admin-home/assets/js/components/paper/resources.js @@ -5,20 +5,24 @@ import { ColumnLinkGroup } from '../linkGroup/column-link-group'; import { useAdminContext } from '../../hooks/use-admin-context'; export const Resources = () => { - const { adminSettings: { resourcesData: { community = [], resources = [] } = {} } = {} } = useAdminContext(); + const { + adminSettings: { + resourcesData: { community = [], resources = [] } = {}, + } = {}, + } = useAdminContext(); return ( - + diff --git a/modules/admin-home/assets/js/components/paper/site-parts.js b/modules/admin-home/assets/js/components/paper/site-parts.js index 31767d08..4d07a129 100644 --- a/modules/admin-home/assets/js/components/paper/site-parts.js +++ b/modules/admin-home/assets/js/components/paper/site-parts.js @@ -5,25 +5,29 @@ import { __ } from '@wordpress/i18n'; import { useAdminContext } from '../../hooks/use-admin-context'; export const SiteParts = () => { - const { adminSettings: { siteParts: { siteParts = [], sitePages = [], general = [] } = {} } = {} } = useAdminContext(); + const { + adminSettings: { + siteParts: { siteParts = [], sitePages = [], general = [] } = {}, + } = {}, + } = useAdminContext(); return ( - + diff --git a/modules/admin-home/assets/js/components/paper/welcome.js b/modules/admin-home/assets/js/components/paper/welcome.js index 952ea419..4b75466f 100644 --- a/modules/admin-home/assets/js/components/paper/welcome.js +++ b/modules/admin-home/assets/js/components/paper/welcome.js @@ -8,117 +8,159 @@ import { useEffect, useRef, useState } from 'react'; import Box from '@elementor/ui/Box'; import CircularProgress from '@elementor/ui/CircularProgress'; -export const Welcome = ( { sx, dismissable = false } ) => { - const { adminSettings: { - config: { nonceInstall = '', disclaimer = '', slug = '' } = {}, - welcome: { title = '', text = '', buttons = [], image: { src = '', alt = '' } = {} } = {}, - } = {}, +export const Welcome = ({ sx, dismissable = false }) => { + const { + adminSettings: { + config: { nonceInstall = '', disclaimer = '', slug = '' } = {}, + welcome: { + title = '', + text = '', + buttons = [], + image: { src = '', alt = '' } = {}, + } = {}, + } = {}, } = useAdminContext(); - const [ isLoading, setIsLoading ] = useState( false ); - const [ visible, setVisible ] = useState( true ); - const [ imageWidth, setImageWidth ] = useState( 578 ); - const parentRef = useRef( null ); + const [isLoading, setIsLoading] = useState(false); + const [visible, setVisible] = useState(true); + const [imageWidth, setImageWidth] = useState(578); + const parentRef = useRef(null); - useEffect( () => { + useEffect(() => { const handleResize = () => { - if ( parentRef.current ) { + if (parentRef.current) { const parentWidth = parentRef.current.offsetWidth; - setImageWidth( parentWidth < 800 ? 400 : 578 ); + setImageWidth(parentWidth < 800 ? 400 : 578); } }; handleResize(); - window.addEventListener( 'resize', handleResize ); + window.addEventListener('resize', handleResize); return () => { - window.removeEventListener( 'resize', handleResize ); + window.removeEventListener('resize', handleResize); }; - }, [] ); + }, []); - if ( ! title || ! visible ) { + if (!title || !visible) { return null; } return ( - - { dismissable && ( - { - try { - await wp.ajax.post( 'ehe_dismiss_theme_notice', { nonce: window.ehe_cb.nonce } ); - setVisible( false ); - } catch ( e ) { - } - } }> - { __( 'Dismiss this notice.', 'hello-elementor' ) } + + {dismissable && ( + { + try { + await wp.ajax.post('ehe_dismiss_theme_notice', { + nonce: window.ehe_cb.nonce, + }); + setVisible(false); + } catch (e) {} + }} + > + + {__('Dismiss this notice.', 'hello-elementor')} + - ) } - - - { title } - - { text } + )} + + + + {title} - - { - buttons.map( ( { linkText, link, variant, color, target = '' } ) => { - const onClick = async () => { - if ( 'install' === link ) { - try { - const data = { - _wpnonce: nonceInstall, - slug, - }; + + {text} + + + {buttons.map(({ linkText, link, variant, color, target = '' }) => { + const onClick = async () => { + if ('install' === link) { + try { + const data = { + _wpnonce: nonceInstall, + slug, + }; - setIsLoading( true ); + setIsLoading(true); - const response = await wp.ajax.post( 'ehe_install_elementor', data ); + const response = await wp.ajax.post( + 'ehe_install_elementor', + data, + ); - if ( response.activateUrl ) { - window.location.href = response.activateUrl; - } else { - throw new Error(); + if (response.activateUrl) { + window.location.href = response.activateUrl; + } else { + throw new Error(); + } + } catch (error) { + // eslint-disable-next-line no-alert + alert( + __( + 'Currently the plugin isn’t available. Please try again later. You can also contact our support at: wordpress.org/plugins/hello-plus', + 'hello-elementor', + ), + ); + } finally { + setIsLoading(false); } - } catch ( error ) { - // eslint-disable-next-line no-alert - alert( __( 'Currently the plugin isn’t available. Please try again later. You can also contact our support at: wordpress.org/plugins/hello-plus', 'hello-elementor' ) ); - } finally { - setIsLoading( false ); + } else { + window.open(link, target || '_self'); } - } else { - window.open( link, target || '_self' ); - } - }; + }; - return ( - - ); - } ) - } + ) : ( + linkText + )} + + ); + })} - { disclaimer && ( - { disclaimer } - ) } + {disclaimer && ( + + {disclaimer} + + )} - { src && ( ) } + {src && ( + + )} ); diff --git a/modules/admin-home/assets/js/components/promotions/feature.js b/modules/admin-home/assets/js/components/promotions/feature.js index 4137747a..a9f864b8 100644 --- a/modules/admin-home/assets/js/components/promotions/feature.js +++ b/modules/admin-home/assets/js/components/promotions/feature.js @@ -2,13 +2,11 @@ import Stack from '@elementor/ui/Stack'; import CheckedCircleIcon from '@elementor/icons/CheckedCircleIcon'; import Typography from '@elementor/ui/Typography'; -export const Feature = ( { text } ) => { +export const Feature = ({ text }) => { return ( - + - - { text } - + {text} ); }; diff --git a/modules/admin-home/assets/js/components/promotions/list.js b/modules/admin-home/assets/js/components/promotions/list.js index 50fe106e..1f987d20 100644 --- a/modules/admin-home/assets/js/components/promotions/list.js +++ b/modules/admin-home/assets/js/components/promotions/list.js @@ -6,10 +6,10 @@ export const PromotionsList = () => { const { promotionsLinks } = useAdminContext(); return ( - - { promotionsLinks.map( ( link, i ) => { - return ; - } ) } + + {promotionsLinks.map((link, i) => { + return ; + })} ); }; diff --git a/modules/admin-home/assets/js/components/settings/changelog.js b/modules/admin-home/assets/js/components/settings/changelog.js index 72e824f8..b61ec71e 100644 --- a/modules/admin-home/assets/js/components/settings/changelog.js +++ b/modules/admin-home/assets/js/components/settings/changelog.js @@ -9,34 +9,32 @@ import Divider from '@elementor/ui/Divider'; import { PlusIcon } from './plus-icon'; import { Fragment } from 'react'; -export const ChangelogDialog = ( { open, onClose, whatsNew } ) => { +export const ChangelogDialog = ({ open, onClose, whatsNew }) => { return ( - - } - > - - { __( 'Changelog', 'hello-elementor' ) } + + }> + + {__('Changelog', 'hello-elementor')} - - - { whatsNew.map( ( item, index ) => { - return ( - - - { index !== whatsNew.length - 1 && } - - ); - } ) } + + + {whatsNew.map((item, index) => { + return ( + + + {index !== whatsNew.length - 1 && } + + ); + })} diff --git a/modules/admin-home/assets/js/components/settings/plus-icon.js b/modules/admin-home/assets/js/components/settings/plus-icon.js index 5d1c8685..048d4481 100644 --- a/modules/admin-home/assets/js/components/settings/plus-icon.js +++ b/modules/admin-home/assets/js/components/settings/plus-icon.js @@ -1,9 +1,9 @@ import SvgIcon from '@elementor/ui/SvgIcon'; import { ReactComponent as Icon } from '../../../images/plus.svg'; -export const PlusIcon = ( props ) => { +export const PlusIcon = (props) => { return ( - + ); diff --git a/modules/admin-home/assets/js/components/settings/seo.js b/modules/admin-home/assets/js/components/settings/seo.js index 58d474b5..cf27ba36 100644 --- a/modules/admin-home/assets/js/components/settings/seo.js +++ b/modules/admin-home/assets/js/components/settings/seo.js @@ -7,36 +7,57 @@ import { Spinner } from '@wordpress/components'; export const Seo = () => { const { - themeSettings: { SKIP_LINK: skipLink, DESCRIPTION_META_TAG: descriptionMetaTag }, + themeSettings: { + SKIP_LINK: skipLink, + DESCRIPTION_META_TAG: descriptionMetaTag, + }, updateSetting, isLoading, } = useSettingsContext(); - if ( isLoading ) { + if (isLoading) { return ; } return ( - - - { __( 'These settings affect how search engines and assistive technologies interact with your website.', 'hello-elementor' ) } + + + {__( + 'These settings affect how search engines and assistive technologies interact with your website.', + 'hello-elementor', + )} updateSetting( 'DESCRIPTION_META_TAG', ! descriptionMetaTag ) } - description={ __( 'What it does: Removes the description meta tag code from singular content pages.', 'hello-elementor' ) } - code={ '' } - tip={ __( 'Tip: If you use an SEO plugin that handles meta descriptions, like Yoast or Rank Math, disable this option to prevent duplicate meta tags.', 'hello-elementor' ) } + value={descriptionMetaTag} + label={__('Disable description meta tag', 'hello-elementor')} + onSwitchClick={() => + updateSetting('DESCRIPTION_META_TAG', !descriptionMetaTag) + } + description={__( + 'What it does: Removes the description meta tag code from singular content pages.', + 'hello-elementor', + )} + code={''} + tip={__( + 'Tip: If you use an SEO plugin that handles meta descriptions, like Yoast or Rank Math, disable this option to prevent duplicate meta tags.', + 'hello-elementor', + )} /> updateSetting( 'SKIP_LINK', ! skipLink ) } - description={ __( 'What it does: Removes the "Skip to content" link that helps screen reader users and keyboard navigators jump directly to the main content.', 'hello-elementor' ) } - code={ '' } - tip={ __( 'Tip: If you use an accessibility plugin that adds a "skip to content" link, disable this option to prevent duplications.', 'hello-elementor' ) } + value={skipLink} + label={__('Disable skip links', 'hello-elementor')} + onSwitchClick={() => updateSetting('SKIP_LINK', !skipLink)} + description={__( + 'What it does: Removes the "Skip to content" link that helps screen reader users and keyboard navigators jump directly to the main content.', + 'hello-elementor', + )} + code={ + '' + } + tip={__( + 'Tip: If you use an accessibility plugin that adds a "skip to content" link, disable this option to prevent duplications.', + 'hello-elementor', + )} /> ); diff --git a/modules/admin-home/assets/js/components/settings/setting.js b/modules/admin-home/assets/js/components/settings/setting.js index c2c99403..4bee0f8f 100644 --- a/modules/admin-home/assets/js/components/settings/setting.js +++ b/modules/admin-home/assets/js/components/settings/setting.js @@ -3,31 +3,60 @@ import Box from '@elementor/ui/Box'; import Typography from '@elementor/ui/Typography'; import Switch from '@elementor/ui/Switch'; -export const Setting = ( { label, value, onSwitchClick, code, description, tip } ) => { +export const Setting = ({ + label, + value, + onSwitchClick, + code, + description, + tip, +}) => { return ( - - - - - + + + + + - - - { label } + + + + {label} + - - + + - + - { description } - { tip } - - { code } + + {description} + + + {tip} + + + {code} diff --git a/modules/admin-home/assets/js/components/settings/settings-page.js b/modules/admin-home/assets/js/components/settings/settings-page.js index 7c5f1282..8b7645ae 100644 --- a/modules/admin-home/assets/js/components/settings/settings-page.js +++ b/modules/admin-home/assets/js/components/settings/settings-page.js @@ -22,102 +22,125 @@ import { ChangelogDialog } from './changelog'; const Notices = () => { const notices = useSelect( - ( select ) => - select( noticesStore ) + (select) => + select(noticesStore) .getNotices() - .filter( ( notice ) => 'snackbar' === notice.type ), + .filter((notice) => 'snackbar' === notice.type), [], ); - useEffect( () => { - setOpen( true ); - }, [ notices ] ); + useEffect(() => { + setOpen(true); + }, [notices]); - const [ open, setOpen ] = useState( true ); - const { removeNotice } = useDispatch( noticesStore ); + const [open, setOpen] = useState(true); + const { removeNotice } = useDispatch(noticesStore); const onClose = () => { removeNotice(); - setOpen( false ); + setOpen(false); }; - return ( - notices.map( ( notice ) => { - const { content, id, status } = notice; + return notices.map((notice) => { + const { content, id, status } = notice; - return ( - - - { content } - - - ); - } ) - ); + return ( + + + {content} + + + ); + }); }; -const StyledTab = styled( Tab )( () => ( { +const StyledTab = styled(Tab)(() => ({ '&.Mui-selected': { color: '#C00BB9', }, -} ) ); +})); -const StyledTabs = styled( Tabs )( () => ( { +const StyledTabs = styled(Tabs)(() => ({ '& .MuiTabs-indicator': { backgroundColor: '#C00BB9', }, -} ) ); +})); export const SettingsPage = () => { const { whatsNew } = useSettingsContext(); - const { getTabsProps, getTabProps, getTabPanelProps } = useTabs( 'one' ); - const [ open, setOpen ] = useState( false ); + const { getTabsProps, getTabProps, getTabPanelProps } = useTabs('one'); + const [open, setOpen] = useState(false); - const handleOpen = ( event ) => { + const handleOpen = (event) => { event.preventDefault(); - setOpen( true ); + setOpen(true); }; - const handleClose = () => setOpen( false ); + const handleClose = () => setOpen(false); return ( <> - - - - + + + - { __( 'Advanced theme settings', 'hello-elementor' ) } + {__('Advanced theme settings', 'hello-elementor')} - handleOpen( event ) } color={ 'primary' }> - { __( 'Changelog', 'hello-elementor' ) } + handleOpen(event)} + color={'primary'} + > + {__('Changelog', 'hello-elementor')} - - { __( 'Advanced settings are available for experienced users and developers. If you\'re unsure about a setting, we recommend keeping the default option.', 'hello-elementor' ) } + + {__( + "Advanced settings are available for experienced users and developers. If you're unsure about a setting, we recommend keeping the default option.", + 'hello-elementor', + )} - - - - - + + + + + - - - + + + + + + + + + - + ); }; diff --git a/modules/admin-home/assets/js/components/settings/settings-provider.js b/modules/admin-home/assets/js/components/settings/settings-provider.js index 494da9bd..66627c09 100644 --- a/modules/admin-home/assets/js/components/settings/settings-provider.js +++ b/modules/admin-home/assets/js/components/settings/settings-provider.js @@ -5,74 +5,81 @@ import { dispatch } from '@wordpress/data'; export const SettingsContext = createContext(); -export const SettingsProvider = ( { children } ) => { - const [ isLoading, setIsLoading ] = useState( true ); - const [ themeSettings, setThemeSettings ] = useState( {} ); - const [ settingsUpdated, setSettingsUpdated ] = useState( false ); - const [ whatsNew, setWhatsNew ] = useState( [] ); +export const SettingsProvider = ({ children }) => { + const [isLoading, setIsLoading] = useState(true); + const [themeSettings, setThemeSettings] = useState({}); + const [settingsUpdated, setSettingsUpdated] = useState(false); + const [whatsNew, setWhatsNew] = useState([]); - const updateSetting = ( settingsName, settingsValue ) => { - setThemeSettings( { + const updateSetting = (settingsName, settingsValue) => { + setThemeSettings({ ...themeSettings, - [ settingsName ]: settingsValue, - } ); - setSettingsUpdated( true ); + [settingsName]: settingsValue, + }); + setSettingsUpdated(true); }; - useEffect( () => { - if ( ! settingsUpdated ) { + useEffect(() => { + if (!settingsUpdated) { return; } - setIsLoading( true ); - apiFetch( { + setIsLoading(true); + apiFetch({ path: '/elementor-hello-elementor/v1/theme-settings', method: 'POST', data: { settings: themeSettings }, - } ).then( async () => { - dispatch( 'core/notices' ).createNotice( - 'success', - __( 'Settings Saved', 'hello-elementor' ), - { - type: 'snackbar', - isDismissible: true, - }, - ); - } ).catch( () => { - dispatch( 'core/notices' ).createNotice( - 'error', - __( 'Error when saving settings', 'hello-elementor' ), - { - type: 'snackbar', - isDismissible: true, - }, - ); - } ).finally( () => { - setIsLoading( false ); - setSettingsUpdated( false ); - } ); - }, [ settingsUpdated, themeSettings ] ); + }) + .then(async () => { + dispatch('core/notices').createNotice( + 'success', + __('Settings Saved', 'hello-elementor'), + { + type: 'snackbar', + isDismissible: true, + }, + ); + }) + .catch(() => { + dispatch('core/notices').createNotice( + 'error', + __('Error when saving settings', 'hello-elementor'), + { + type: 'snackbar', + isDismissible: true, + }, + ); + }) + .finally(() => { + setIsLoading(false); + setSettingsUpdated(false); + }); + }, [settingsUpdated, themeSettings]); - useEffect( () => { - Promise.all( [ - apiFetch( { path: '/elementor-hello-elementor/v1/theme-settings' } ), - apiFetch( { path: '/elementor-hello-elementor/v1/whats-new' } ), - ] ).then( ( [ settings, whatsNewData ] ) => { - setWhatsNew( whatsNewData ); - setThemeSettings( settings.settings ); - } ).finally( () => { - setIsLoading( false ); - } ); - }, [] ); + useEffect(() => { + Promise.all([ + apiFetch({ path: '/elementor-hello-elementor/v1/theme-settings' }), + apiFetch({ path: '/elementor-hello-elementor/v1/whats-new' }), + ]) + .then(([settings, whatsNewData]) => { + setWhatsNew(whatsNewData); + setThemeSettings(settings.settings); + }) + .finally(() => { + setIsLoading(false); + }); + }, []); return ( - - { children } + + {children} ); }; diff --git a/modules/admin-home/assets/js/components/settings/structure.js b/modules/admin-home/assets/js/components/settings/structure.js index 81e14263..56fbf7ea 100644 --- a/modules/admin-home/assets/js/components/settings/structure.js +++ b/modules/admin-home/assets/js/components/settings/structure.js @@ -10,35 +10,52 @@ export const Structure = () => { themeSettings: { HEADER_FOOTER: headerFooter, PAGE_TITLE: pageTitle }, updateSetting, isLoading, - } = useSettingsContext(); - if ( isLoading ) { + if (isLoading) { return ; } return ( - - - { __( 'These settings relate to the structure of your pages.', 'hello-elementor' ) } + + + {__( + 'These settings relate to the structure of your pages.', + 'hello-elementor', + )} updateSetting( 'HEADER_FOOTER', ! headerFooter ) } - description={ __( 'What it does: Removes the theme’s default header and footer sections from every page, along with their associated CSS/JS files.', 'hello-elementor' ) } - code={ '\n' + - '
...
' } - tip={ __( 'Tip: If you use a plugin like Elementor Pro for your headers and footers, disable the theme header and footer to improve performance.', 'hello-elementor' ) } + value={headerFooter} + label={__('Disable theme header and footer', 'hello-elementor')} + onSwitchClick={() => updateSetting('HEADER_FOOTER', !headerFooter)} + description={__( + 'What it does: Removes the theme’s default header and footer sections from every page, along with their associated CSS/JS files.', + 'hello-elementor', + )} + code={ + '\n' + + '
...
' + } + tip={__( + 'Tip: If you use a plugin like Elementor Pro for your headers and footers, disable the theme header and footer to improve performance.', + 'hello-elementor', + )} /> updateSetting( 'PAGE_TITLE', ! pageTitle ) } - description={ __( 'What it does: Removes the main page title above your page content.', 'hello-elementor' ) } - code={ '' } - tip={ __( 'Tip: If you do not want to display page titles or are using Elementor widgets to display your page titles, hide the page title.', 'hello-elementor' ) } + value={pageTitle} + label={__('Hide page title', 'hello-elementor')} + onSwitchClick={() => updateSetting('PAGE_TITLE', !pageTitle)} + description={__( + 'What it does: Removes the main page title above your page content.', + 'hello-elementor', + )} + code={ + '' + } + tip={__( + 'Tip: If you do not want to display page titles or are using Elementor widgets to display your page titles, hide the page title.', + 'hello-elementor', + )} />
); diff --git a/modules/admin-home/assets/js/components/settings/theme.js b/modules/admin-home/assets/js/components/settings/theme.js index 29559cd5..18f08e6d 100644 --- a/modules/admin-home/assets/js/components/settings/theme.js +++ b/modules/admin-home/assets/js/components/settings/theme.js @@ -13,34 +13,51 @@ export const Theme = () => { isLoading, } = useSettingsContext(); - if ( isLoading ) { + if (isLoading) { return ; } return ( - - - { __( 'These settings allow you to change or remove default Hello Elementor theme styles.', 'hello-elementor' ) } + + + {__( + 'These settings allow you to change or remove default Hello Elementor theme styles.', + 'hello-elementor', + )} - - { __( 'Be careful, disabling these settings could break your website.', 'hello-elementor' ) } + + {__( + 'Be careful, disabling these settings could break your website.', + 'hello-elementor', + )} updateSetting( 'HELLO_STYLE', ! helloStyle ) } - description={ __( 'What it does: Turns off CSS reset rules by disabling the theme’s reset stylesheet. CSS reset rules make sure your website looks the same in different browsers.', 'hello-elementor' ) } - code={ `` } - tip={ __( 'Tip: Deregistering reset.css can make your website load faster. Disable it only if you’re using another style reset method, such as with a child theme.', 'hello-elementor' ) } + value={helloStyle} + label={__('Deregister Hello reset.css', 'hello-elementor')} + onSwitchClick={() => updateSetting('HELLO_STYLE', !helloStyle)} + description={__( + 'What it does: Turns off CSS reset rules by disabling the theme’s reset stylesheet. CSS reset rules make sure your website looks the same in different browsers.', + 'hello-elementor', + )} + code={``} + tip={__( + 'Tip: Deregistering reset.css can make your website load faster. Disable it only if you’re using another style reset method, such as with a child theme.', + 'hello-elementor', + )} /> updateSetting( 'HELLO_THEME', ! helloTheme ) } - description={ __( 'What it does: Turns off CSS reset rules by disabling the theme’s reset stylesheet. CSS reset rules make sure your website looks the same in different browsers.', 'hello-elementor' ) } - code={ `` } - tip={ __( 'Tip: Deregistering theme.css can make your website load faster. Disable it only if you are not using any WordPress elements on your website, or if you want to style them yourself. Examples of WordPress elements include comments area, pagination box, and image align classes.', 'hello-elementor' ) } + value={helloTheme} + label={__('Deregister Hello theme.css', 'hello-elementor')} + onSwitchClick={() => updateSetting('HELLO_THEME', !helloTheme)} + description={__( + 'What it does: Turns off CSS reset rules by disabling the theme’s reset stylesheet. CSS reset rules make sure your website looks the same in different browsers.', + 'hello-elementor', + )} + code={``} + tip={__( + 'Tip: Deregistering theme.css can make your website load faster. Disable it only if you are not using any WordPress elements on your website, or if you want to style them yourself. Examples of WordPress elements include comments area, pagination box, and image align classes.', + 'hello-elementor', + )} /> ); diff --git a/modules/admin-home/assets/js/components/settings/update.js b/modules/admin-home/assets/js/components/settings/update.js index 143dcc49..8b741183 100644 --- a/modules/admin-home/assets/js/components/settings/update.js +++ b/modules/admin-home/assets/js/components/settings/update.js @@ -2,18 +2,24 @@ import Box from '@elementor/ui/Box'; import Typography from '@elementor/ui/Typography'; import { useMemo } from 'react'; -export default function Update( { title, description } ) { - const descriptionToShow = useMemo( () => { +export default function Update({ title, description }) { + const descriptionToShow = useMemo(() => { const parser = new DOMParser(); - const doc = parser.parseFromString( description, 'text/html' ); - const listItems = doc.querySelectorAll( 'li' ); - const extractedContent = Array.from( listItems ).map( ( item ) => item.textContent.trim() ); - return extractedContent.join( '\n' ); - }, [ description ] ); - return ( - - { title } - { descriptionToShow } - - ); + const doc = parser.parseFromString(description, 'text/html'); + const listItems = doc.querySelectorAll('li'); + const extractedContent = Array.from(listItems).map((item) => + item.textContent.trim(), + ); + return extractedContent.join('\n'); + }, [description]); + return ( + + + {title} + + + {descriptionToShow} + + + ); } diff --git a/modules/admin-home/assets/js/components/settings/use-settings-context.js b/modules/admin-home/assets/js/components/settings/use-settings-context.js index 63700048..68681ea4 100644 --- a/modules/admin-home/assets/js/components/settings/use-settings-context.js +++ b/modules/admin-home/assets/js/components/settings/use-settings-context.js @@ -2,5 +2,5 @@ import { useContext } from 'react'; import { SettingsContext } from './settings-provider'; export const useSettingsContext = () => { - return useContext( SettingsContext ); + return useContext(SettingsContext); }; diff --git a/modules/admin-home/assets/js/components/spinner/spinner.js b/modules/admin-home/assets/js/components/spinner/spinner.js index 38a548ab..1db8bcd7 100644 --- a/modules/admin-home/assets/js/components/spinner/spinner.js +++ b/modules/admin-home/assets/js/components/spinner/spinner.js @@ -16,7 +16,7 @@ const Spinner = () => { }; return ( - + ); diff --git a/modules/admin-home/assets/js/components/top-bar/top-bar-content.js b/modules/admin-home/assets/js/components/top-bar/top-bar-content.js index 353e4ece..02ca8698 100644 --- a/modules/admin-home/assets/js/components/top-bar/top-bar-content.js +++ b/modules/admin-home/assets/js/components/top-bar/top-bar-content.js @@ -3,15 +3,25 @@ import DynamicIcon from '../dynamic-icon'; import Typography from '@elementor/ui/Typography'; import { __ } from '@wordpress/i18n'; -export const TopBarContent = ( { sx = {}, iconSize = 'medium' } ) => { +export const TopBarContent = ({ sx = {}, iconSize = 'medium' }) => { return ( - - - - { __( 'Hello', 'hello-elementor' ) } + + + + + {__('Hello', 'hello-elementor')} + ); diff --git a/modules/admin-home/assets/js/components/top-bar/top-bar.js b/modules/admin-home/assets/js/components/top-bar/top-bar.js index 1a069b13..29f89591 100644 --- a/modules/admin-home/assets/js/components/top-bar/top-bar.js +++ b/modules/admin-home/assets/js/components/top-bar/top-bar.js @@ -3,7 +3,18 @@ import { TopBarContent } from './top-bar-content'; export const TopBar = () => { return ( - + ); diff --git a/modules/admin-home/assets/js/hello-elementor-admin.js b/modules/admin-home/assets/js/hello-elementor-admin.js index d86f1aed..5c161809 100644 --- a/modules/admin-home/assets/js/hello-elementor-admin.js +++ b/modules/admin-home/assets/js/hello-elementor-admin.js @@ -7,8 +7,17 @@ import Container from '@elementor/ui/Container'; const App = () => { return ( - - + + @@ -16,12 +25,11 @@ const App = () => { ); }; -document.addEventListener( 'DOMContentLoaded', () => { - const container = document.getElementById( 'ehe-admin-home' ); +document.addEventListener('DOMContentLoaded', () => { + const container = document.getElementById('ehe-admin-home'); - if ( container ) { - const root = createRoot( container ); - root.render( ); + if (container) { + const root = createRoot(container); + root.render(); } -} ); - +}); diff --git a/modules/admin-home/assets/js/hello-elementor-conversion-banner.js b/modules/admin-home/assets/js/hello-elementor-conversion-banner.js index 5f13d414..57281e2e 100644 --- a/modules/admin-home/assets/js/hello-elementor-conversion-banner.js +++ b/modules/admin-home/assets/js/hello-elementor-conversion-banner.js @@ -8,37 +8,39 @@ const App = () => { return ( - - + + ); }; -document.addEventListener( 'DOMContentLoaded', () => { - const container = document.getElementById( 'ehe-admin-cb' ); +document.addEventListener('DOMContentLoaded', () => { + const container = document.getElementById('ehe-admin-cb'); - if ( container ) { - const { beforeWrap = false } = window.ehe_cb; - const { selector, before = false } = window.ehe_cb.data; - const headerEnd = document.querySelector( selector ); + if (container) { + const { beforeWrap = false } = window.ehe_cb; + const { selector, before = false } = window.ehe_cb.data; + const headerEnd = document.querySelector(selector); - if ( headerEnd ) { - if ( beforeWrap ) { - const wrapElement = document.querySelector( '.wrap' ); - if ( wrapElement ) { - wrapElement.insertAdjacentElement( 'beforebegin', container ); + if (headerEnd) { + if (beforeWrap) { + const wrapElement = document.querySelector('.wrap'); + if (wrapElement) { + wrapElement.insertAdjacentElement('beforebegin', container); } - } else if ( before ) { - headerEnd.insertAdjacentElement( 'beforebegin', container ); - } else { - headerEnd.insertAdjacentElement( 'afterend', container ); - } + } else if (before) { + headerEnd.insertAdjacentElement('beforebegin', container); + } else { + headerEnd.insertAdjacentElement('afterend', container); + } } - const root = createRoot( container ); - root.render( ); + const root = createRoot(container); + root.render(); } -} ); - +}); diff --git a/modules/admin-home/assets/js/hello-elementor-menu.js b/modules/admin-home/assets/js/hello-elementor-menu.js index 07dceb63..e2e8a361 100644 --- a/modules/admin-home/assets/js/hello-elementor-menu.js +++ b/modules/admin-home/assets/js/hello-elementor-menu.js @@ -1,7 +1,8 @@ -document.addEventListener( 'DOMContentLoaded', () => { - const links = document.querySelectorAll( 'a[href="admin.php?page=hello-elementor-ai-site-planner"], a[href="admin.php?page=hello-elementor-theme-builder"]' ); - links.forEach( ( link ) => { - link.setAttribute( 'target', '_blank' ); - } ); -} ); - +document.addEventListener('DOMContentLoaded', () => { + const links = document.querySelectorAll( + 'a[href="admin.php?page=hello-elementor-ai-site-planner"], a[href="admin.php?page=hello-elementor-theme-builder"]', + ); + links.forEach((link) => { + link.setAttribute('target', '_blank'); + }); +}); diff --git a/modules/admin-home/assets/js/hello-elementor-settings.js b/modules/admin-home/assets/js/hello-elementor-settings.js index 03dec601..52d80c16 100644 --- a/modules/admin-home/assets/js/hello-elementor-settings.js +++ b/modules/admin-home/assets/js/hello-elementor-settings.js @@ -9,11 +9,17 @@ const App = () => { return ( - + @@ -23,12 +29,11 @@ const App = () => { ); }; -document.addEventListener( 'DOMContentLoaded', () => { - const container = document.getElementById( 'ehe-admin-settings' ); +document.addEventListener('DOMContentLoaded', () => { + const container = document.getElementById('ehe-admin-settings'); - if ( container ) { - const root = createRoot( container ); - root.render( ); + if (container) { + const root = createRoot(container); + root.render(); } -} ); - +}); diff --git a/modules/admin-home/assets/js/hello-elementor-topbar.js b/modules/admin-home/assets/js/hello-elementor-topbar.js index 0c4fcb00..bd6db8b4 100644 --- a/modules/admin-home/assets/js/hello-elementor-topbar.js +++ b/modules/admin-home/assets/js/hello-elementor-topbar.js @@ -2,17 +2,14 @@ import { createRoot } from 'react-dom/client'; import { TopBar } from './components/top-bar/top-bar'; const App = () => { - return ( - - ); + return ; }; -document.addEventListener( 'DOMContentLoaded', () => { - const container = document.getElementById( 'ehe-admin-top-bar-root' ); +document.addEventListener('DOMContentLoaded', () => { + const container = document.getElementById('ehe-admin-top-bar-root'); - if ( container ) { - const root = createRoot( container ); - root.render( ); + if (container) { + const root = createRoot(container); + root.render(); } -} ); - +}); diff --git a/modules/admin-home/assets/js/hooks/use-admin-context.js b/modules/admin-home/assets/js/hooks/use-admin-context.js index aa074c87..64a62ab3 100644 --- a/modules/admin-home/assets/js/hooks/use-admin-context.js +++ b/modules/admin-home/assets/js/hooks/use-admin-context.js @@ -2,5 +2,5 @@ import { useContext } from 'react'; import { AdminContext } from '../providers/admin-provider'; export const useAdminContext = () => { - return useContext( AdminContext ); + return useContext(AdminContext); }; diff --git a/modules/admin-home/assets/js/icons/elementor.tsx b/modules/admin-home/assets/js/icons/elementor.tsx index 5fcba770..8621adf0 100644 --- a/modules/admin-home/assets/js/icons/elementor.tsx +++ b/modules/admin-home/assets/js/icons/elementor.tsx @@ -2,12 +2,18 @@ import * as React from 'react'; import SvgIcon from '@elementor/ui/SvgIcon'; -const BrandElementorIcon = React.forwardRef< SVGSVGElement, React.ComponentProps >( ( props, ref ) => { +const BrandElementorIcon = React.forwardRef< + SVGSVGElement, + React.ComponentProps +>((props, ref) => { return ( - - + + ); -} ); +}); export default BrandElementorIcon; diff --git a/modules/admin-home/assets/js/icons/youtube.tsx b/modules/admin-home/assets/js/icons/youtube.tsx index b616cf1d..017b9190 100644 --- a/modules/admin-home/assets/js/icons/youtube.tsx +++ b/modules/admin-home/assets/js/icons/youtube.tsx @@ -2,9 +2,12 @@ import * as React from 'react'; import SvgIcon from '@elementor/ui/SvgIcon'; -const BrandYoutubeIcon = React.forwardRef< SVGSVGElement, React.ComponentProps>( ( props, ref ) => { +const BrandYoutubeIcon = React.forwardRef< + SVGSVGElement, + React.ComponentProps +>((props, ref) => { return ( - + ); -} ); +}); export default BrandYoutubeIcon; diff --git a/modules/admin-home/assets/js/layouts/grids/grid-with-action-links.js b/modules/admin-home/assets/js/layouts/grids/grid-with-action-links.js index 50228a21..c23aef96 100644 --- a/modules/admin-home/assets/js/layouts/grids/grid-with-action-links.js +++ b/modules/admin-home/assets/js/layouts/grids/grid-with-action-links.js @@ -2,19 +2,36 @@ import Grid from '@elementor/ui/Grid'; import { PromotionsList } from '../../components/promotions/list'; import useMediaQuery from '@elementor/ui/useMediaQuery'; -export const GridWithActionLinks = ( { children } ) => { - const isSmallScreen = useMediaQuery( ( theme ) => theme.breakpoints.down( 'sm' ) ); +export const GridWithActionLinks = ({ children }) => { + const isSmallScreen = useMediaQuery((theme) => theme.breakpoints.down('sm')); return ( - - - { children } + + + {children} - { ! isSmallScreen && ( - + {!isSmallScreen && ( + - ) } + )} ); }; diff --git a/modules/admin-home/assets/js/pages/admin-page.js b/modules/admin-home/assets/js/pages/admin-page.js index d201deef..41a6f091 100644 --- a/modules/admin-home/assets/js/pages/admin-page.js +++ b/modules/admin-home/assets/js/pages/admin-page.js @@ -12,14 +12,13 @@ import { Resources } from '../components/paper/resources'; export const AdminPage = () => { return ( - - + - + - + diff --git a/modules/admin-home/assets/js/providers/admin-provider.js b/modules/admin-home/assets/js/providers/admin-provider.js index 891eef10..c52f81c8 100644 --- a/modules/admin-home/assets/js/providers/admin-provider.js +++ b/modules/admin-home/assets/js/providers/admin-provider.js @@ -3,30 +3,34 @@ import apiFetch from '@wordpress/api-fetch'; export const AdminContext = createContext(); -export const AdminProvider = ( { children } ) => { - const [ isLoading, setIsLoading ] = React.useState( true ); - const [ promotionsLinks, setPromotionsLinks ] = React.useState( [] ); - const [ adminSettings, setAdminSettings ] = React.useState( {} ); +export const AdminProvider = ({ children }) => { + const [isLoading, setIsLoading] = React.useState(true); + const [promotionsLinks, setPromotionsLinks] = React.useState([]); + const [adminSettings, setAdminSettings] = React.useState({}); - useEffect( () => { - Promise.all( [ - apiFetch( { path: '/elementor-hello-elementor/v1/promotions' } ), - apiFetch( { path: '/elementor-hello-elementor/v1/admin-settings' } ), - ] ).then( ( [ links, settings ] ) => { - setPromotionsLinks( links.links ); - setAdminSettings( settings.config ); - } ).finally( () => { - setIsLoading( false ); - } ); - }, [] ); + useEffect(() => { + Promise.all([ + apiFetch({ path: '/elementor-hello-elementor/v1/promotions' }), + apiFetch({ path: '/elementor-hello-elementor/v1/admin-settings' }), + ]) + .then(([links, settings]) => { + setPromotionsLinks(links.links); + setAdminSettings(settings.config); + }) + .finally(() => { + setIsLoading(false); + }); + }, []); return ( - - { children } + + {children} ); }; diff --git a/package-lock.json b/package-lock.json index 14c9ec29..9038b03d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "devDependencies": { "@elementor/wp-lite-env": "^0.0.20", "@playwright/test": "^1.52.0", + "@typescript-eslint/eslint-plugin": "^8.62.0", "@typescript-eslint/parser": "^8.18.0", "@wordpress/components": "^29.9.0", "@wordpress/env": "^10.26.0", @@ -27,10 +28,13 @@ "@wordpress/scripts": "^30.16.0", "copy-webpack-plugin": "^13.0.0", "dotenv": "^16.5.0", + "eslint-config-prettier": "^10.1.8", "eslint-plugin-babel": "^5.3.1", "eslint-plugin-jsx-a11y": "^6.10.2", "eslint-plugin-no-jquery": "^3.1.1", + "eslint-plugin-prettier": "^5.5.5", "eslint-plugin-react": "^7.37.5", + "prettier": "^3.4.1", "typescript": "^5.8.3", "webpack-cli": "^6.0.1", "webpack-remove-empty-scripts": "^1.0.4" @@ -2549,9 +2553,9 @@ } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", - "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2581,9 +2585,9 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", - "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", "dev": true, "license": "MIT", "engines": { @@ -5214,13 +5218,13 @@ } }, "node_modules/@pkgr/core": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", - "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.3.6.tgz", + "integrity": "sha512-SEeaJLb3qBNF/OaXnaR1NmmBbFYk1zC0ZH/52fATcRPLFg/p791YrcyFFy44Bo9sLaGuSuLp5Q6axbb/O+v/RA==", "dev": true, "license": "MIT", "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + "node": "^14.18.0 || >=16.0.0" }, "funding": { "url": "https://opencollective.com/pkgr" @@ -6523,18 +6527,106 @@ "@types/node": "*" } }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.62.0.tgz", + "integrity": "sha512-o+mpz7EYiMzXoySXiKmzlabIvTVqUuK5yLrAedRPRDA0IpPFMUV1IXt6OqljIxX/kumN6EjUYp41Hqelh6p/Dw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.62.0", + "@typescript-eslint/type-utils": "8.62.0", + "@typescript-eslint/utils": "8.62.0", + "@typescript-eslint/visitor-keys": "8.62.0", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.62.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/type-utils": { + "version": "8.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.62.0.tgz", + "integrity": "sha512-+g5O3j0w2ldzC86Pv6fvbO/xhAonbJFIdf/MKQ1d30gndlsVzUOE83ldfSE15Qrl9fhFjK6AovHs5Wpp6vx86w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.62.0", + "@typescript-eslint/typescript-estree": "8.62.0", + "@typescript-eslint/utils": "8.62.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/utils": { + "version": "8.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.62.0.tgz", + "integrity": "sha512-82r66fi9zYwZ+mTq3vKgwjbZ1PVk/DJzrXFLpG6RnBbdvH8TEGVHIs9H4d2drhkOzf0syZuD/OZvvlu6GDbP4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.62.0", + "@typescript-eslint/types": "8.62.0", + "@typescript-eslint/typescript-estree": "8.62.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/@typescript-eslint/parser": { - "version": "8.44.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.44.1.tgz", - "integrity": "sha512-EHrrEsyhOhxYt8MTg4zTF+DJMuNBzWwgvvOYNj/zm1vnaD/IC5zCXFehZv94Piqa2cRFfXrTFxIvO95L7Qc/cw==", + "version": "8.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.62.0.tgz", + "integrity": "sha512-dzHeT2gySzZtLDsuqxU9AkYgIsQoHAHtRBpOqM+Ofzx1Bwrd2RcCjQJ+6iQbsHOIR6NS33bF2W1k3blN1zLDrA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.44.1", - "@typescript-eslint/types": "8.44.1", - "@typescript-eslint/typescript-estree": "8.44.1", - "@typescript-eslint/visitor-keys": "8.44.1", - "debug": "^4.3.4" + "@typescript-eslint/scope-manager": "8.62.0", + "@typescript-eslint/types": "8.62.0", + "@typescript-eslint/typescript-estree": "8.62.0", + "@typescript-eslint/visitor-keys": "8.62.0", + "debug": "^4.4.3" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -6544,20 +6636,20 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.44.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.44.1.tgz", - "integrity": "sha512-ycSa60eGg8GWAkVsKV4E6Nz33h+HjTXbsDT4FILyL8Obk5/mx4tbvCNsLf9zret3ipSumAOG89UcCs/KRaKYrA==", + "version": "8.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.62.0.tgz", + "integrity": "sha512-wexnCqiTg7BOGtbLDftYpRWlmLq4xfoMd7BKFR6Y75sZS3QmRKLdN3yWLhmIYgqMmP/OXWpj3H8odkb5nGURCQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.44.1", - "@typescript-eslint/types": "^8.44.1", - "debug": "^4.3.4" + "@typescript-eslint/tsconfig-utils": "^8.62.0", + "@typescript-eslint/types": "^8.62.0", + "debug": "^4.4.3" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -6567,18 +6659,18 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.44.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.44.1.tgz", - "integrity": "sha512-NdhWHgmynpSvyhchGLXh+w12OMT308Gm25JoRIyTZqEbApiBiQHD/8xgb6LqCWCFcxFtWwaVdFsLPQI3jvhywg==", + "version": "8.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.62.0.tgz", + "integrity": "sha512-1lX38kNxXIRb8mEc3lbq5mdHq1Pf2+U0nFU65KfT18mtPxxl0fvjuEE92mHuXPuCtElJhOrddOpyMlM3Z0umEA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.44.1", - "@typescript-eslint/visitor-keys": "8.44.1" + "@typescript-eslint/types": "8.62.0", + "@typescript-eslint/visitor-keys": "8.62.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -6589,9 +6681,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.44.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.44.1.tgz", - "integrity": "sha512-B5OyACouEjuIvof3o86lRMvyDsFwZm+4fBOqFHccIctYgBjqR3qT39FBYGN87khcgf0ExpdCBeGKpKRhSFTjKQ==", + "version": "8.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.62.0.tgz", + "integrity": "sha512-y2GAdB6ykaXUvuspbYnizQc4oDDz0Tz/Yc7iWrXf9mx8vm/L/0vLHCe0tS2boG96Zy+DivnVDQ9ZUEWoHqqx1g==", "dev": true, "license": "MIT", "engines": { @@ -6602,7 +6694,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/type-utils": { @@ -6750,9 +6842,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.44.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.44.1.tgz", - "integrity": "sha512-Lk7uj7y9uQUOEguiDIDLYLJOrYHQa7oBiURYVFqIpGxclAFQ78f6VUOM8lI2XEuNOKNB7XuvM2+2cMXAoq4ALQ==", + "version": "8.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.62.0.tgz", + "integrity": "sha512-KvAclkktORPvM54TgLgA4z9HIV1M8zOgw9ZVNXl9f/8dLYfXYX1wkMXP7qmabpijQRV5bHJLOmoyGQbLMaUYeg==", "dev": true, "license": "MIT", "engines": { @@ -6764,22 +6856,21 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.44.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.44.1.tgz", - "integrity": "sha512-qnQJ+mVa7szevdEyvfItbO5Vo+GfZ4/GZWWDRRLjrxYPkhM+6zYB2vRYwCsoJLzqFCdZT4mEqyJoyzkunsZ96A==", + "version": "8.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.62.0.tgz", + "integrity": "sha512-+hVbNxtW64pIcZWDPGbyaKF7vp2IBTVY5ma1blwwksrjdsbdqqEKvJWMGbBofei4F6Dovx1M0RJgoFeNu2279A==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.44.1", - "@typescript-eslint/tsconfig-utils": "8.44.1", - "@typescript-eslint/types": "8.44.1", - "@typescript-eslint/visitor-keys": "8.44.1", - "debug": "^4.3.4", - "fast-glob": "^3.3.2", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^2.1.0" + "@typescript-eslint/project-service": "8.62.0", + "@typescript-eslint/tsconfig-utils": "8.62.0", + "@typescript-eslint/types": "8.62.0", + "@typescript-eslint/visitor-keys": "8.62.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -6789,13 +6880,52 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, "license": "ISC", "bin": { @@ -6966,14 +7096,14 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.44.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.44.1.tgz", - "integrity": "sha512-576+u0QD+Jp3tZzvfRfxon0EA2lzcDt3lhUbsC6Lgzy9x2VR4E+JUiNyGHi5T8vk0TV+fpJ5GLG1JsJuWCaKhw==", + "version": "8.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.62.0.tgz", + "integrity": "sha512-CY3uyFSRbcQv3nnSv8S0+lDftMVz6P963PoRlxrV7ew/Md564g9ut60PYzdLM5qW4jFn93GBF+Soi90ISAN+GQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.44.1", - "eslint-visitor-keys": "^4.2.1" + "@typescript-eslint/types": "8.62.0", + "eslint-visitor-keys": "^5.0.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -8034,6 +8164,19 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@wordpress/eslint-plugin/node_modules/eslint-config-prettier": { + "version": "8.10.2", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.10.2.tgz", + "integrity": "sha512-/IGJ6+Dka158JnP5n5YFMOszjDWrXggGz1LaK/guZq9vZTmniaKlHcsscvkAhn9y4U+BU3JuUdYvtAMcv30y4A==", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, "node_modules/@wordpress/eslint-plugin/node_modules/eslint-plugin-jest": { "version": "27.9.0", "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-27.9.0.tgz", @@ -12829,14 +12972,17 @@ } }, "node_modules/eslint-config-prettier": { - "version": "8.10.2", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.10.2.tgz", - "integrity": "sha512-/IGJ6+Dka158JnP5n5YFMOszjDWrXggGz1LaK/guZq9vZTmniaKlHcsscvkAhn9y4U+BU3JuUdYvtAMcv30y4A==", + "version": "10.1.8", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", + "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", "dev": true, "license": "MIT", "bin": { "eslint-config-prettier": "bin/cli.js" }, + "funding": { + "url": "https://opencollective.com/eslint-config-prettier" + }, "peerDependencies": { "eslint": ">=7.0.0" } @@ -13090,14 +13236,14 @@ } }, "node_modules/eslint-plugin-prettier": { - "version": "5.5.4", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.4.tgz", - "integrity": "sha512-swNtI95SToIz05YINMA6Ox5R057IMAmWZ26GqPxusAp1TZzj+IdY9tXNWWD3vkF/wEqydCONcwjTFpxybBqZsg==", + "version": "5.5.6", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.6.tgz", + "integrity": "sha512-ifetmTcxWfz+4qRW3pH/ujdTq2jQIj59AxJMIN26K5avYgU8dxycUETQonWiW+wPrYXA0j3Try0l1CnwVQtDqQ==", "dev": true, "license": "MIT", "dependencies": { - "prettier-linter-helpers": "^1.0.0", - "synckit": "^0.11.7" + "prettier-linter-helpers": "^1.0.1", + "synckit": "^0.11.13" }, "engines": { "node": "^14.18.0 || >=16.0.0" @@ -13256,13 +13402,13 @@ } }, "node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", "dev": true, "license": "Apache-2.0", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://opencollective.com/eslint" @@ -20294,7 +20440,6 @@ "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", "dev": true, "license": "MIT", - "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -20306,9 +20451,9 @@ } }, "node_modules/prettier-linter-helpers": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", - "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.1.tgz", + "integrity": "sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==", "dev": true, "license": "MIT", "dependencies": { @@ -23362,13 +23507,13 @@ "license": "MIT" }, "node_modules/synckit": { - "version": "0.11.11", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz", - "integrity": "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==", + "version": "0.11.13", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.13.tgz", + "integrity": "sha512-eNRKgb3z66Yp3D2CixVujOUvXLFUTij/zVnV8KRyvFdQwpz7I5DS8UfRkTeLzb64u+dkzDSdelE24izu+zSSUg==", "dev": true, "license": "MIT", "dependencies": { - "@pkgr/core": "^0.2.9" + "@pkgr/core": "^0.3.6" }, "engines": { "node": "^14.18.0 || >=16.0.0" @@ -23915,9 +24060,9 @@ } }, "node_modules/ts-api-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", - "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", "dev": true, "license": "MIT", "engines": { diff --git a/package.json b/package.json index 89ea04b3..d09792a9 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "start": "wp-scripts start", "build:dev": "composer update && composer install && wp-scripts build --env=developemntWithWatch", "build:prod": "composer install --no-dev && wp-scripts build --env=production", + "format": "wp-scripts format", "lint": "npm run lint:css && npm run lint:js && npm run lint:php", "lint:css": "wp-scripts lint-style", "lint:css:fix": "wp-scripts lint-style --fix", @@ -35,6 +36,7 @@ "devDependencies": { "@elementor/wp-lite-env": "^0.0.20", "@playwright/test": "^1.52.0", + "@typescript-eslint/eslint-plugin": "^8.62.0", "@typescript-eslint/parser": "^8.18.0", "@wordpress/components": "^29.9.0", "@wordpress/env": "^10.26.0", @@ -44,10 +46,13 @@ "@wordpress/scripts": "^30.16.0", "copy-webpack-plugin": "^13.0.0", "dotenv": "^16.5.0", + "eslint-config-prettier": "^10.1.8", "eslint-plugin-babel": "^5.3.1", "eslint-plugin-jsx-a11y": "^6.10.2", "eslint-plugin-no-jquery": "^3.1.1", + "eslint-plugin-prettier": "^5.5.5", "eslint-plugin-react": "^7.37.5", + "prettier": "^3.4.1", "typescript": "^5.8.3", "webpack-cli": "^6.0.1", "webpack-remove-empty-scripts": "^1.0.4" diff --git a/tests/playwright/assets/api-requests.ts b/tests/playwright/assets/api-requests.ts index b2089040..5ae94537 100644 --- a/tests/playwright/assets/api-requests.ts +++ b/tests/playwright/assets/api-requests.ts @@ -6,57 +6,56 @@ import { Image, Post, WpPage, User } from '../types/types.ts'; export default class ApiRequests { private readonly nonce: string; private readonly baseUrl: string; - constructor( baseUrl: string, nonce: string ) { + constructor(baseUrl: string, nonce: string) { this.nonce = nonce; this.baseUrl = baseUrl; } - public async create( request: APIRequestContext, entity: string, data: Post ) { - const response = await request.post( `${ this.baseUrl }/index.php`, { - params: { rest_route: `/wp/v2/${ entity }` }, + public async create(request: APIRequestContext, entity: string, data: Post) { + const response = await request.post(`${this.baseUrl}/index.php`, { + params: { rest_route: `/wp/v2/${entity}` }, headers: { 'X-WP-Nonce': this.nonce, }, multipart: data, - } ); - - if ( ! response.ok() ) { - throw new Error( ` - Failed to create a ${ entity }: ${ response.status() }. - ${ await response.text() } - ${ response.url() } - TEST_PARALLEL_INDEX: ${ process.env.TEST_PARALLEL_INDEX } - NONCE: ${ this.nonce } - ` ); + }); + + if (!response.ok()) { + throw new Error(` + Failed to create a ${entity}: ${response.status()}. + ${await response.text()} + ${response.url()} + TEST_PARALLEL_INDEX: ${process.env.TEST_PARALLEL_INDEX} + NONCE: ${this.nonce} + `); } const { id } = await response.json(); return id; } - public async createMedia( request: APIRequestContext, image: Image ) { + public async createMedia(request: APIRequestContext, image: Image) { const imagePath = image.filePath; - const response = await request.post( `${ this.baseUrl }/index.php`, { - + const response = await request.post(`${this.baseUrl}/index.php`, { params: { rest_route: '/wp/v2/media' }, headers: { 'X-WP-Nonce': this.nonce, }, multipart: { - file: fs.createReadStream( imagePath ), + file: fs.createReadStream(imagePath), title: image.title, status: 'publish', description: image.description, altText: image.alt_text, caption: image.caption, }, - } ); + }); - if ( ! response.ok() ) { - throw new Error( ` - Failed to create default media: ${ response.status() }. - ${ await response.text() } - ` ); + if (!response.ok()) { + throw new Error(` + Failed to create default media: ${response.status()}. + ${await response.text()} + `); } const { id } = await response.json(); @@ -64,40 +63,53 @@ export default class ApiRequests { return id; } - public async deleteMedia( request: APIRequestContext, ids: string[] ) { + public async deleteMedia(request: APIRequestContext, ids: string[]) { const requests = []; - for ( const id in ids ) { - requests.push( request.delete( `${ this.baseUrl }/index.php`, { - headers: { - 'X-WP-Nonce': this.nonce, - }, - params: { - rest_route: `/wp/v2/media/${ ids[ id ] }`, - force: 1, - }, - } ) ); + for (const id in ids) { + requests.push( + request.delete(`${this.baseUrl}/index.php`, { + headers: { + 'X-WP-Nonce': this.nonce, + }, + params: { + rest_route: `/wp/v2/media/${ids[id]}`, + force: 1, + }, + }), + ); } - await Promise.all( requests ); + await Promise.all(requests); } - public async cleanUpTestPages( request: APIRequestContext, shouldDeleteAllPages = false ) { - const pagesPublished = await this.getPages( request ), - pagesDraft = await this.getPages( request, 'draft' ), - pages = [ ...pagesPublished, ...pagesDraft ]; + public async cleanUpTestPages( + request: APIRequestContext, + shouldDeleteAllPages = false, + ) { + const pagesPublished = await this.getPages(request), + pagesDraft = await this.getPages(request, 'draft'), + pages = [...pagesPublished, ...pagesDraft]; const pageIds = pages - .filter( ( page: WpPage ) => shouldDeleteAllPages || page.title.rendered.includes( 'Playwright Test Page' ) ) - .map( ( page: WpPage ) => page.id ); - - for ( const id of pageIds ) { - await this.deletePage( request, id ); + .filter( + (page: WpPage) => + shouldDeleteAllPages || + page.title.rendered.includes('Playwright Test Page'), + ) + .map((page: WpPage) => page.id); + + for (const id of pageIds) { + await this.deletePage(request, id); } } - public async installPlugin( request: APIRequestContext, slug: string, active: boolean ) { - const response = await request.post( `${ this.baseUrl }/index.php`, { + public async installPlugin( + request: APIRequestContext, + slug: string, + active: boolean, + ) { + const response = await request.post(`${this.baseUrl}/index.php`, { params: { rest_route: `/wp/v2/plugins`, slug, @@ -106,169 +118,186 @@ export default class ApiRequests { headers: { 'X-WP-Nonce': this.nonce, }, - } ); - - if ( ! response.ok() ) { - throw new Error( ` - Failed to install a plugin: ${ response ? response.status() : '' }. - ${ response ? await response.text() : '' } - slug: ${ slug } - ` ); + }); + + if (!response.ok()) { + throw new Error(` + Failed to install a plugin: ${response ? response.status() : ''}. + ${response ? await response.text() : ''} + slug: ${slug} + `); } const { plugin } = await response.json(); return plugin; } - public async deactivatePlugin( request: APIRequestContext, slug: string ) { - const response = await request.post( `${ this.baseUrl }/index.php`, { + public async deactivatePlugin(request: APIRequestContext, slug: string) { + const response = await request.post(`${this.baseUrl}/index.php`, { params: { - rest_route: `/wp/v2/plugins/${ slug }`, + rest_route: `/wp/v2/plugins/${slug}`, status: 'inactive', }, headers: { 'X-WP-Nonce': this.nonce, }, - } ); - if ( ! response.ok() ) { - throw new Error( ` - Failed to deactivate a plugin: ${ response ? response.status() : '' }. - ${ response ? await response.text() : '' } - slug: ${ slug } - ` ); + }); + if (!response.ok()) { + throw new Error(` + Failed to deactivate a plugin: ${response ? response.status() : ''}. + ${response ? await response.text() : ''} + slug: ${slug} + `); } } - public async deletePlugin( request: APIRequestContext, slug: string ) { - const response = await this._delete( request, 'plugins', slug ); + public async deletePlugin(request: APIRequestContext, slug: string) { + const response = await this._delete(request, 'plugins', slug); - if ( ! response.ok() ) { - throw new Error( ` - Failed to delete a plugin: ${ response ? response.status() : '' }. - ${ response ? await response.text() : '' } - slug: ${ slug } - ` ); + if (!response.ok()) { + throw new Error(` + Failed to delete a plugin: ${response ? response.status() : ''}. + ${response ? await response.text() : ''} + slug: ${slug} + `); } } - public async getTheme( request: APIRequestContext, status?: 'active' | 'inactive' ) { - return await this.get( request, 'themes', status ); + public async getTheme( + request: APIRequestContext, + status?: 'active' | 'inactive', + ) { + return await this.get(request, 'themes', status); } - public async customGet( request: APIRequestContext, restRoute: string, multipart? ) { - const response = await request.get( `${ this.baseUrl }/${ restRoute }`, { + public async customGet( + request: APIRequestContext, + restRoute: string, + multipart?, + ) { + const response = await request.get(`${this.baseUrl}/${restRoute}`, { headers: { 'X-WP-Nonce': this.nonce, }, multipart, - } ); + }); - if ( ! response.ok() ) { - throw new Error( ` - Failed to get from ${ restRoute }: ${ response.status() }. - ${ this.baseUrl } - ` ); + if (!response.ok()) { + throw new Error(` + Failed to get from ${restRoute}: ${response.status()}. + ${this.baseUrl} + `); } return await response.json(); } - public async customPut( request: APIRequestContext, restRoute: string, data ) { - const response = await request.put( `${ this.baseUrl }/${ restRoute }`, { + public async customPut(request: APIRequestContext, restRoute: string, data) { + const response = await request.put(`${this.baseUrl}/${restRoute}`, { headers: { 'X-WP-Nonce': this.nonce, }, data, - } ); + }); - if ( ! response.ok() ) { - throw new Error( ` - Failed to put to ${ restRoute }: ${ response.status() }. - ${ await response.text() } - ` ); + if (!response.ok()) { + throw new Error(` + Failed to put to ${restRoute}: ${response.status()}. + ${await response.text()} + `); } } - private async get( request: APIRequestContext, entity: string, status: string = 'publish' ) { - const response = await request.get( `${ this.baseUrl }/index.php`, { + private async get( + request: APIRequestContext, + entity: string, + status: string = 'publish', + ) { + const response = await request.get(`${this.baseUrl}/index.php`, { params: { - rest_route: `/wp/v2/${ entity }`, + rest_route: `/wp/v2/${entity}`, status, }, headers: { 'X-WP-Nonce': this.nonce, }, - } ); + }); - if ( ! response.ok() ) { - throw new Error( ` - Failed to get a ${ entity }: ${ response.status() }. - ${ await response.text() } - ` ); + if (!response.ok()) { + throw new Error(` + Failed to get a ${entity}: ${response.status()}. + ${await response.text()} + `); } return await response.json(); } - private async getPages( request: APIRequestContext, status: string = 'publish' ) { - return await this.get( request, 'pages', status ); + private async getPages( + request: APIRequestContext, + status: string = 'publish', + ) { + return await this.get(request, 'pages', status); } - private async deletePage( request: APIRequestContext, pageId: string ) { - await this._delete( request, 'pages', pageId ); + private async deletePage(request: APIRequestContext, pageId: string) { + await this._delete(request, 'pages', pageId); } - public async deleteUser( request: APIRequestContext, userId: string ) { - const response = await request.delete( `${ this.baseUrl }/index.php`, { + public async deleteUser(request: APIRequestContext, userId: string) { + const response = await request.delete(`${this.baseUrl}/index.php`, { headers: { 'X-WP-Nonce': this.nonce, }, params: { - rest_route: `/wp/v2/users/${ userId }`, + rest_route: `/wp/v2/users/${userId}`, force: true, reassign: '-1', }, - } ); + }); - if ( ! response.ok() ) { - throw new Error( ` - Failed to delete a user with id: ${ userId }: ${ response.status() }. - ${ await response.text() } - ` ); + if (!response.ok()) { + throw new Error(` + Failed to delete a user with id: ${userId}: ${response.status()}. + ${await response.text()} + `); } return await response.json(); } - private async _delete( request: APIRequestContext, entity: string, id: string ) { - const response = await request.delete( `${ this.baseUrl }/index.php`, { + private async _delete( + request: APIRequestContext, + entity: string, + id: string, + ) { + const response = await request.delete(`${this.baseUrl}/index.php`, { params: { - rest_route: `/wp/v2/${ entity }/${ id }`, + rest_route: `/wp/v2/${entity}/${id}`, }, headers: { 'X-WP-Nonce': this.nonce, }, - } ); + }); - if ( ! response.ok() ) { - throw new Error( ` - Failed to delete a ${ entity } with id '${ id }': ${ response.status() }. - ${ await response.text() } - ` ); + if (!response.ok()) { + throw new Error(` + Failed to delete a ${entity} with id '${id}': ${response.status()}. + ${await response.text()} + `); } return response; } - public async createNewUser( request: APIRequestContext, user: User ) { - const randomNumber = crypto.randomInt( 0, 1000 ); - const username = `${ user.username }${ randomNumber }`, + public async createNewUser(request: APIRequestContext, user: User) { + const randomNumber = crypto.randomInt(0, 1000); + const username = `${user.username}${randomNumber}`, email = user.email || username + '@example.com', password = user.password || 'password', roles = user.roles; - const response = await request.post( `${ this.baseUrl }/index.php`, { - + const response = await request.post(`${this.baseUrl}/index.php`, { params: { rest_route: '/wp/v2/users' }, headers: { 'X-WP-Nonce': this.nonce, @@ -277,15 +306,15 @@ export default class ApiRequests { username, email, password, - roles: [ ...roles ], + roles: [...roles], }, - } ); + }); - if ( ! response.ok() ) { - throw new Error( ` - Failed to create new user: ${ response.status() }. - ${ await response.text() } - ` ); + if (!response.ok()) { + throw new Error(` + Failed to create new user: ${response.status()}. + ${await response.text()} + `); } const { id } = await response.json(); diff --git a/tests/playwright/assets/breakpoints.ts b/tests/playwright/assets/breakpoints.ts index 8164bdb7..1b898242 100644 --- a/tests/playwright/assets/breakpoints.ts +++ b/tests/playwright/assets/breakpoints.ts @@ -5,85 +5,111 @@ import EditorSelectors from '../selectors/editor-selectors.ts'; export default class { readonly page: Page; - constructor( page: Page ) { + constructor(page: Page) { this.page = page; } - static getDeviceLocator( page: Page, device: Device ): Locator { - const baseLocator = page.locator( '[aria-label="Switch Device"]' ); - return baseLocator.locator( `[data-testid="switch-device-to-${ device }"]` ); + static getDeviceLocator(page: Page, device: Device): Locator { + const baseLocator = page.locator('[aria-label="Switch Device"]'); + return baseLocator.locator(`[data-testid="switch-device-to-${device}"]`); } static getAll(): Device[] { - return [ 'mobile', 'mobile_extra', 'tablet', 'tablet_extra', 'laptop', 'desktop', 'widescreen' ]; + return [ + 'mobile', + 'mobile_extra', + 'tablet', + 'tablet_extra', + 'laptop', + 'desktop', + 'widescreen', + ]; } static getBasic(): Device[] { - return [ 'mobile', 'tablet', 'desktop' ]; + return ['mobile', 'tablet', 'desktop']; } - async saveOrUpdate( editor: EditorPage, toReload = false ) { + async saveOrUpdate(editor: EditorPage, toReload = false) { const hasTopBar: boolean = await editor.hasTopBar(); - if ( hasTopBar ) { - await editor.saveSiteSettingsWithTopBar( toReload ); + if (hasTopBar) { + await editor.saveSiteSettingsWithTopBar(toReload); } else { await editor.saveSiteSettingsNoTopBar(); } } - async addAllBreakpoints( editor: EditorPage, experimentPostId?: string ) { - await editor.openSiteSettings( 'settings-layout' ); - await editor.openSection( 'section_breakpoints' ); - await this.page.waitForSelector( 'text=Active Breakpoints' ); - - const devices = [ 'Mobile Landscape', 'Tablet Landscape', 'Laptop', 'Widescreen' ]; - - for ( const device of devices ) { - if ( await this.page.$( '.select2-selection__e-plus-button' ) ) { - await this.page.click( '.select2-selection__e-plus-button' ); - await this.page.click( `li:has-text("${ device }")` ); + async addAllBreakpoints(editor: EditorPage, experimentPostId?: string) { + await editor.openSiteSettings('settings-layout'); + await editor.openSection('section_breakpoints'); + await this.page.waitForSelector('text=Active Breakpoints'); + + const devices = [ + 'Mobile Landscape', + 'Tablet Landscape', + 'Laptop', + 'Widescreen', + ]; + + for (const device of devices) { + if (await this.page.$('.select2-selection__e-plus-button')) { + await this.page.click('.select2-selection__e-plus-button'); + await this.page.click(`li:has-text("${device}")`); } } - await this.saveOrUpdate( editor, true ); + await this.saveOrUpdate(editor, true); - if ( experimentPostId ) { - await this.page.goto( `/wp-admin/post.php?post=${ experimentPostId }&action=elementor` ); + if (experimentPostId) { + await this.page.goto( + `/wp-admin/post.php?post=${experimentPostId}&action=elementor`, + ); } else { await this.page.reload(); - if ( await this.page.$( '#elementor-panel-header-kit-close' ) ) { - await this.page.locator( '#elementor-panel-header-kit-close' ).click( { timeout: 30000 } ); + if (await this.page.$('#elementor-panel-header-kit-close')) { + await this.page + .locator('#elementor-panel-header-kit-close') + .click({ timeout: 30000 }); } } - await this.page.waitForSelector( '#elementor-editor-wrapper' ); + await this.page.waitForSelector('#elementor-editor-wrapper'); } - async resetBreakpoints( editor: EditorPage ) { - await editor.openSiteSettings( 'settings-layout' ); - await editor.openSection( 'section_breakpoints' ); - await this.page.waitForSelector( 'text=Active Breakpoints' ); + async resetBreakpoints(editor: EditorPage) { + await editor.openSiteSettings('settings-layout'); + await editor.openSection('section_breakpoints'); + await this.page.waitForSelector('text=Active Breakpoints'); - const removeBreakpointButton = EditorSelectors.panels.siteSettings.layout.breakpoints.removeBreakpointButton; - while ( await this.page.locator( removeBreakpointButton ).count() > 0 ) { - await this.page.click( removeBreakpointButton ); + const removeBreakpointButton = + EditorSelectors.panels.siteSettings.layout.breakpoints + .removeBreakpointButton; + while ((await this.page.locator(removeBreakpointButton).count()) > 0) { + await this.page.click(removeBreakpointButton); } - await this.saveOrUpdate( editor, true ); + await this.saveOrUpdate(editor, true); } - getBreakpointInputLocator( page: Page, device: BreakpointEditableDevice ): Locator { - return page.locator( `input[data-setting="viewport_${ device }"]` ); + getBreakpointInputLocator( + page: Page, + device: BreakpointEditableDevice, + ): Locator { + return page.locator(`input[data-setting="viewport_${device}"]`); } - async setBreakpoint( editor: EditorPage, device: BreakpointEditableDevice, value: number ) { - await editor.openSiteSettings( 'settings-layout' ); - await editor.openSection( 'section_breakpoints' ); - await this.page.waitForSelector( 'text=Active Breakpoints' ); - - const locator = this.getBreakpointInputLocator( this.page, device ); - await locator.fill( String( value ) ); - await this.saveOrUpdate( editor ); - await this.page.locator( EditorSelectors.toast ).waitFor(); + async setBreakpoint( + editor: EditorPage, + device: BreakpointEditableDevice, + value: number, + ) { + await editor.openSiteSettings('settings-layout'); + await editor.openSection('section_breakpoints'); + await this.page.waitForSelector('text=Active Breakpoints'); + + const locator = this.getBreakpointInputLocator(this.page, device); + await locator.fill(String(value)); + await this.saveOrUpdate(editor); + await this.page.locator(EditorSelectors.toast).waitFor(); } } diff --git a/tests/playwright/assets/elements-utils.ts b/tests/playwright/assets/elements-utils.ts index 27800a87..98d2447c 100644 --- a/tests/playwright/assets/elements-utils.ts +++ b/tests/playwright/assets/elements-utils.ts @@ -12,36 +12,42 @@ import { $eType, ElementorType } from '../types/types.ts'; let parent: unknown; let elementor: ElementorType; let $e: $eType; -export const addElement = ( props: { model: unknown, container: null | string, isContainerASection: boolean } ): string | undefined => { - if ( props.container ) { - parent = elementor.getContainer( props.container ); +export const addElement = (props: { + model: unknown; + container: null | string; + isContainerASection: boolean; +}): string | undefined => { + if (props.container) { + parent = elementor.getContainer(props.container); } else { // If a `container` isn't supplied - create a new Section. - parent = $e.run( - 'document/elements/create', - { - model: { elType: 'section' }, - columns: 1, - container: elementor.getContainer( 'document' ), - }, - ); + parent = $e.run('document/elements/create', { + model: { elType: 'section' }, + columns: 1, + container: elementor.getContainer('document'), + }); props.isContainerASection = true; } - if ( props.isContainerASection && 'object' === typeof parent && 'children' in parent ) { - parent = parent.children[ 0 ]; + if ( + props.isContainerASection && + 'object' === typeof parent && + 'children' in parent + ) { + parent = parent.children[0]; } - const element = $e.run( - 'document/elements/create', - { - model: props.model, - container: parent, - }, - ); + const element = $e.run('document/elements/create', { + model: props.model, + container: parent, + }); - if ( 'object' === typeof element && 'id' in element && 'string' === typeof element.id ) { + if ( + 'object' === typeof element && + 'id' in element && + 'string' === typeof element.id + ) { return element.id; } return undefined; @@ -54,6 +60,6 @@ export const addElement = ( props: { model: unknown, container: null | string, i * * @return {string} css selector */ -export const getElementSelector = ( id: string ) => { - return `[data-id = "${ id }"]`; +export const getElementSelector = (id: string) => { + return `[data-id = "${id}"]`; }; diff --git a/tests/playwright/assets/wp-cli.ts b/tests/playwright/assets/wp-cli.ts index 0178d54b..b711ea5f 100644 --- a/tests/playwright/assets/wp-cli.ts +++ b/tests/playwright/assets/wp-cli.ts @@ -1,6 +1,6 @@ -export const wpCli = async ( command: string ) => { - const port = ( 1 === Number( process.env.TEST_PARALLEL_INDEX ) ) ? 8889 : 8888; +export const wpCli = async (command: string) => { + const port = 1 === Number(process.env.TEST_PARALLEL_INDEX) ? 8889 : 8888; - const { cli } = await import( '@elementor/wp-lite-env' ); - await cli( port, command ); + const { cli } = await import('@elementor/wp-lite-env'); + await cli(port, command); }; diff --git a/tests/playwright/pages/base-page.ts b/tests/playwright/pages/base-page.ts index 473b55cf..b0d7b8ff 100644 --- a/tests/playwright/pages/base-page.ts +++ b/tests/playwright/pages/base-page.ts @@ -7,9 +7,9 @@ export default class BasePage { * @param {import('@playwright/test').Page} page * @param {import('@playwright/test').TestInfo} testInfo */ - constructor( page: Page, testInfo: TestInfo ) { - if ( ! page || ! testInfo ) { - throw new Error( 'Page and TestInfo must be provided' ); + constructor(page: Page, testInfo: TestInfo) { + if (!page || !testInfo) { + throw new Error('Page and TestInfo must be provided'); } /** @@ -22,28 +22,28 @@ export default class BasePage { */ this.testInfo = testInfo; - const { baseURL, proxy } = this.testInfo.config.projects[ 0 ].use; + const { baseURL, proxy } = this.testInfo.config.projects[0].use; // If wordpress is not located on the domain's top-level (e.g: http://local.host/test-wordpress ), playwright's `baseURL` cannot handle it. - if ( proxy ) { - this.page = new Proxy( this.page, { - get: ( target, key ) => { - switch ( key ) { + if (proxy) { + this.page = new Proxy(this.page, { + get: (target, key) => { + switch (key) { case 'goto': - return ( path: string ) => page.goto( baseURL + path ); + return (path: string) => page.goto(baseURL + path); case 'waitForNavigation': { - return ( args: { url?: string } ) => { - args = ( args.url ) ? { url: baseURL + args.url } : args; + return (args: { url?: string }) => { + args = args.url ? { url: baseURL + args.url } : args; - return page.waitForNavigation( args ); + return page.waitForNavigation(args); }; } } - return target[ key ]; + return target[key]; }, - } ); + }); } } } diff --git a/tests/playwright/pages/editor-page.ts b/tests/playwright/pages/editor-page.ts index c49f75b3..195d5e6e 100644 --- a/tests/playwright/pages/editor-page.ts +++ b/tests/playwright/pages/editor-page.ts @@ -1,13 +1,31 @@ import { readFile } from 'fs/promises'; import { addElement, getElementSelector } from '../assets/elements-utils.ts'; -import { expect, type Page, type Frame, type TestInfo, type ElementHandle, Locator } from '@playwright/test'; +import { + expect, + type Page, + type Frame, + type TestInfo, + type ElementHandle, + Locator, +} from '@playwright/test'; import BasePage from './base-page.ts'; import EditorSelectors from '../selectors/editor-selectors.ts'; import _path, { resolve as pathResolve } from 'path'; // eslint-disable-next-line import/no-extraneous-dependencies import { getComparator } from 'playwright-core/lib/utils'; -import { $eType, Device, WindowType, BackboneType, ElementorType, GapControl, ContainerType, ContainerPreset } from '../types/types.ts'; -import TopBarSelectors, { TopBarSelector } from '../selectors/top-bar-selectors.ts'; +import { + $eType, + Device, + WindowType, + BackboneType, + ElementorType, + GapControl, + ContainerType, + ContainerPreset, +} from '../types/types.ts'; +import TopBarSelectors, { + TopBarSelector, +} from '../selectors/top-bar-selectors.ts'; import Breakpoints from '../assets/breakpoints.ts'; import { timeouts } from '../config/timeouts.ts'; @@ -27,8 +45,12 @@ export default class EditorPage extends BasePage { * @param {TestInfo} testInfo - Test information. * @param {number} cleanPostId - Optional. Post ID. */ - constructor( page: Page, testInfo: TestInfo, cleanPostId: null | number = null ) { - super( page, testInfo ); + constructor( + page: Page, + testInfo: TestInfo, + cleanPostId: null | number = null, + ) { + super(page, testInfo); this.previewFrame = this.getPreviewFrame(); this.postId = cleanPostId; } @@ -40,9 +62,9 @@ export default class EditorPage extends BasePage { * * @return {Promise} */ - async gotoPostId( id: number|string = this.postId ): Promise { - await this.page.goto( `wp-admin/post.php?post=${ id }&action=elementor` ); - await this.page.waitForLoadState( 'load' ); + async gotoPostId(id: number | string = this.postId): Promise { + await this.page.goto(`wp-admin/post.php?post=${id}&action=elementor`); + await this.page.waitForLoadState('load'); await this.waitForPanelToLoad(); } @@ -53,12 +75,15 @@ export default class EditorPage extends BasePage { * * @return {JSON} The updated template data with current dates. */ - updateImageDates( templateData: JSON ): JSON { + updateImageDates(templateData: JSON): JSON { const date = new Date(); - const month = date.toLocaleString( 'default', { month: '2-digit' } ); - const data = JSON.stringify( templateData ); - const updatedData = data.replace( /[0-9]{4}\/[0-9]{2}/g, `${ date.getFullYear() }/${ month }` ); - return JSON.parse( updatedData ) as JSON; + const month = date.toLocaleString('default', { month: '2-digit' }); + const data = JSON.stringify(templateData); + const updatedData = data.replace( + /[0-9]{4}\/[0-9]{2}/g, + `${date.getFullYear()}/${month}`, + ); + return JSON.parse(updatedData) as JSON; } /** @@ -71,14 +96,21 @@ export default class EditorPage extends BasePage { * * @return {Promise} */ - async uploadSVG( svgFileName?: string ): Promise { - const _svgFileName = svgFileName === undefined ? 'test-svg-wide' : svgFileName; - const regex = new RegExp( _svgFileName ); - const response = this.page.waitForResponse( regex ); - await this.page.setInputFiles( EditorSelectors.media.imageInp, _path.resolve( __dirname, `../resources/${ _svgFileName }.svg` ) ); + async uploadSVG(svgFileName?: string): Promise { + const _svgFileName = + svgFileName === undefined ? 'test-svg-wide' : svgFileName; + const regex = new RegExp(_svgFileName); + const response = this.page.waitForResponse(regex); + await this.page.setInputFiles( + EditorSelectors.media.imageInp, + _path.resolve(__dirname, `../resources/${_svgFileName}.svg`), + ); await response; - await this.page.getByRole( 'button', { name: 'Insert Media' } ) - .or( this.page.getByRole( 'button', { name: 'Select' } ) ).nth( 1 ).click(); + await this.page + .getByRole('button', { name: 'Insert Media' }) + .or(this.page.getByRole('button', { name: 'Select' })) + .nth(1) + .click(); } /** @@ -87,29 +119,32 @@ export default class EditorPage extends BasePage { * @param {string} filePath - Path to the template file. * @param {boolean} updateDatesForImages - Optional. Whether to update images dates. Default is false. */ - async loadTemplate( filePath: string, updateDatesForImages: boolean = false ): Promise { - const rawFileData = await readFile( filePath ); - let templateData = JSON.parse( rawFileData.toString() ); + async loadTemplate( + filePath: string, + updateDatesForImages: boolean = false, + ): Promise { + const rawFileData = await readFile(filePath); + let templateData = JSON.parse(rawFileData.toString()); // For templates that use images, date when image is uploaded is hardcoded in template. // Element regression tests upload images before each test. // To update dates in template, use a flag updateDatesForImages = true - if ( updateDatesForImages ) { - templateData = this.updateImageDates( templateData ); + if (updateDatesForImages) { + templateData = this.updateImageDates(templateData); } - await this.page.evaluate( ( data ) => { - const model = new Backbone.Model( { title: 'test' } ); + await this.page.evaluate((data) => { + const model = new Backbone.Model({ title: 'test' }); - window.$e.run( 'document/elements/import', { + window.$e.run('document/elements/import', { data, model, options: { at: 0, withPageSettings: false, }, - } ); - }, templateData ); + }); + }, templateData); } /** @@ -118,9 +153,9 @@ export default class EditorPage extends BasePage { * @return {Promise} */ async cleanContent(): Promise { - await this.page.evaluate( () => { - $e.run( 'document/elements/empty', { force: true } ); - } ); + await this.page.evaluate(() => { + $e.run('document/elements/empty', { force: true }); + }); } /** @@ -129,8 +164,10 @@ export default class EditorPage extends BasePage { * @return {Promise} */ async waitForPanelToLoad(): Promise { - await this.page.waitForSelector( '.elementor-panel-loading', { state: 'detached' } ); - await this.page.waitForSelector( '#elementor-loading', { state: 'hidden' } ); + await this.page.waitForSelector('.elementor-panel-loading', { + state: 'detached', + }); + await this.page.waitForSelector('#elementor-loading', { state: 'hidden' }); } /** @@ -142,8 +179,16 @@ export default class EditorPage extends BasePage { * * @return {Promise} Element ID */ - async addElement( model: unknown, container: null | string = null, isContainerASection = false ): Promise { - return await this.page.evaluate( addElement, { model, container, isContainerASection } ); + async addElement( + model: unknown, + container: null | string = null, + isContainerASection = false, + ): Promise { + return await this.page.evaluate(addElement, { + model, + container, + isContainerASection, + }); } /** @@ -153,12 +198,15 @@ export default class EditorPage extends BasePage { * * @return {Promise} */ - async removeElement( elementId: string ): Promise { - await this.page.evaluate( ( { id } ) => { - $e.run( 'document/elements/delete', { - container: elementor.getContainer( id ), - } ); - }, { id: elementId } ); + async removeElement(elementId: string): Promise { + await this.page.evaluate( + ({ id }) => { + $e.run('document/elements/delete', { + container: elementor.getContainer(id), + }); + }, + { id: elementId }, + ); } /** @@ -170,9 +218,17 @@ export default class EditorPage extends BasePage { * * @return {Promise} The widget ID. */ - async addWidget( widgetType: string, container: string = null, isContainerASection: boolean = false ): Promise { - const widgetId = await this.addElement( { widgetType, elType: 'widget' }, container, isContainerASection ); - await this.getPreviewFrame().waitForSelector( `[data-id='${ widgetId }']` ); + async addWidget( + widgetType: string, + container: string = null, + isContainerASection: boolean = false, + ): Promise { + const widgetId = await this.addElement( + { widgetType, elType: 'widget' }, + container, + isContainerASection, + ); + await this.getPreviewFrame().waitForSelector(`[data-id='${widgetId}']`); return widgetId; } @@ -187,12 +243,16 @@ export default class EditorPage extends BasePage { * * @return {Promise} */ - async loadJsonPageTemplate( dirName: string, fileName: string, widgetSelector: string, updateDatesForImages: boolean = false ): Promise { - const filePath = _path.resolve( dirName, `./templates/${ fileName }.json` ); - const rawFileData = await readFile( filePath ); - const templateData = JSON.parse( rawFileData.toString() ); - const pageTemplateData = - { + async loadJsonPageTemplate( + dirName: string, + fileName: string, + widgetSelector: string, + updateDatesForImages: boolean = false, + ): Promise { + const filePath = _path.resolve(dirName, `./templates/${fileName}.json`); + const rawFileData = await readFile(filePath); + const templateData = JSON.parse(rawFileData.toString()); + const pageTemplateData = { content: templateData, page_settings: [], version: '0.4', @@ -203,24 +263,24 @@ export default class EditorPage extends BasePage { // For templates that use images, date when image is uploaded is hardcoded in template. // Element regression tests upload images before each test. // To update dates in template, use a flag updateDatesForImages = true - if ( updateDatesForImages ) { - this.updateImageDates( templateData ); + if (updateDatesForImages) { + this.updateImageDates(templateData); } - await this.page.evaluate( ( data ) => { - const model = new Backbone.Model( { title: 'test' } ); + await this.page.evaluate((data) => { + const model = new Backbone.Model({ title: 'test' }); - window.$e.run( 'document/elements/import', { + window.$e.run('document/elements/import', { data, model, options: { at: 0, withPageSettings: false, }, - } ); - }, pageTemplateData ); + }); + }, pageTemplateData); - await this.waitForElement( false, widgetSelector ); + await this.waitForElement(false, widgetSelector); } /** @@ -230,16 +290,20 @@ export default class EditorPage extends BasePage { * * @return {Promise | null>} element handle */ - async getElementHandle( id: string ): Promise | null> { - return this.getPreviewFrame().$( getElementSelector( id ) ); + async getElementHandle( + id: string, + ): Promise | null> { + return this.getPreviewFrame().$(getElementSelector(id)); } async waitForPreviewFrame(): Promise { - await this.page.waitForSelector( '[id="elementor-preview-iframe"]', { timeout: timeouts.longAction } ); + await this.page.waitForSelector('[id="elementor-preview-iframe"]', { + timeout: timeouts.longAction, + }); - const frame = this.page.frame( { name: 'elementor-preview-iframe' } ); - if ( ! frame ) { - throw new Error( 'Iframe is null even after it appeared in the DOM.' ); + const frame = this.page.frame({ name: 'elementor-preview-iframe' }); + if (!frame) { + throw new Error('Iframe is null even after it appeared in the DOM.'); } return frame; @@ -251,7 +315,7 @@ export default class EditorPage extends BasePage { * @return {Frame} The preview iframe element. */ getPreviewFrame(): Frame { - return this.page.frame( { name: 'elementor-preview-iframe' } ); + return this.page.frame({ name: 'elementor-preview-iframe' }); } /** @@ -261,15 +325,20 @@ export default class EditorPage extends BasePage { * * @return {Promise} element; */ - async selectElement( elementId: string ): Promise { - await this.page.evaluate( ( { id } ) => { - $e.run( 'document/elements/select', { - container: elementor.getContainer( id ), - } ); - }, { id: elementId } ); + async selectElement(elementId: string): Promise { + await this.page.evaluate( + ({ id }) => { + $e.run('document/elements/select', { + container: elementor.getContainer(id), + }); + }, + { id: elementId }, + ); - await this.getPreviewFrame().waitForSelector( '.elementor-element-' + elementId + '.elementor-element-editable' ); - return this.getPreviewFrame().locator( '.elementor-element-' + elementId ); + await this.getPreviewFrame().waitForSelector( + '.elementor-element-' + elementId + '.elementor-element-editable', + ); + return this.getPreviewFrame().locator('.elementor-element-' + elementId); } /** @@ -280,11 +349,14 @@ export default class EditorPage extends BasePage { * * @return {Promise} */ - async addNewContainerPreset( element: ContainerType, preset: ContainerPreset ): Promise { + async addNewContainerPreset( + element: ContainerType, + preset: ContainerPreset, + ): Promise { const frame = this.getPreviewFrame(); - await frame.locator( '.elementor-add-section-button' ).click(); - await frame.locator( `.${ element }-preset-button` ).click(); - await frame.locator( `[data-preset=${ preset }]` ).click(); + await frame.locator('.elementor-add-section-button').click(); + await frame.locator(`.${element}-preset-button`).click(); + await frame.locator(`[data-preset=${preset}]`).click(); } /** @@ -294,16 +366,22 @@ export default class EditorPage extends BasePage { * * @return {Promise} */ - async openAddElementSection( elementId: string ): Promise { - const element = this.getPreviewFrame().locator( `.elementor-edit-mode .elementor-element-${ elementId }` ); + async openAddElementSection(elementId: string): Promise { + const element = this.getPreviewFrame().locator( + `.elementor-edit-mode .elementor-element-${elementId}`, + ); await element.hover(); - const elementAddButton = this.getPreviewFrame().locator( `.elementor-edit-mode .elementor-element-${ elementId } > .elementor-element-overlay > .elementor-editor-element-settings > .elementor-editor-element-add` ); + const elementAddButton = this.getPreviewFrame().locator( + `.elementor-edit-mode .elementor-element-${elementId} > .elementor-element-overlay > .elementor-editor-element-settings > .elementor-editor-element-add`, + ); await elementAddButton.click(); - await this.getPreviewFrame().waitForSelector( '.elementor-add-section-inline' ); + await this.getPreviewFrame().waitForSelector( + '.elementor-add-section-inline', + ); } - async setWidgetTab( tab: 'content' | 'style' | 'advanced' ): Promise { - await this.page.locator( `.elementor-tab-control-${ tab }` ).click(); + async setWidgetTab(tab: 'content' | 'style' | 'advanced'): Promise { + await this.page.locator(`.elementor-tab-control-${tab}`).click(); } /** @@ -313,16 +391,20 @@ export default class EditorPage extends BasePage { * * @return {Promise} */ - async openPanelTab( panelId: string ): Promise { - await this.page.waitForSelector( `.elementor-tab-control-${ panelId } span` ); + async openPanelTab(panelId: string): Promise { + await this.page.waitForSelector(`.elementor-tab-control-${panelId} span`); // Check if panel has been activated already. - if ( await this.page.$( `.elementor-tab-control-${ panelId }.elementor-active` ) ) { + if ( + await this.page.$(`.elementor-tab-control-${panelId}.elementor-active`) + ) { return; } - await this.page.locator( `.elementor-tab-control-${ panelId } span` ).click(); - await this.page.waitForSelector( `.elementor-tab-control-${ panelId }.elementor-active` ); + await this.page.locator(`.elementor-tab-control-${panelId} span`).click(); + await this.page.waitForSelector( + `.elementor-tab-control-${panelId}.elementor-active`, + ); } /** @@ -332,25 +414,26 @@ export default class EditorPage extends BasePage { * * @return {Promise} */ - async openV2PanelTab( sectionName: 'style' | 'general' ) { - const selectorMap: Record< 'style' | 'general', string > = { + async openV2PanelTab(sectionName: 'style' | 'general') { + const selectorMap: Record<'style' | 'general', string> = { style: 'style', general: 'settings', }; - const sectionButtonSelector = `#tab-0-${ selectorMap[ sectionName ] }`, - sectionContentSelector = `#tabpanel-0-${ selectorMap[ sectionName ] }`, - isOpenSection = await this.page.evaluate( ( selector ) => { - const sectionContentElement: HTMLElement = document.querySelector( selector ); + const sectionButtonSelector = `#tab-0-${selectorMap[sectionName]}`, + sectionContentSelector = `#tabpanel-0-${selectorMap[sectionName]}`, + isOpenSection = await this.page.evaluate((selector) => { + const sectionContentElement: HTMLElement = + document.querySelector(selector); - return ! sectionContentElement?.hidden; - }, sectionContentSelector ); + return !sectionContentElement?.hidden; + }, sectionContentSelector); - if ( isOpenSection ) { + if (isOpenSection) { return; } - await this.page.locator( sectionButtonSelector ).click(); - await this.page.locator( sectionContentSelector ).waitFor(); + await this.page.locator(sectionButtonSelector).click(); + await this.page.locator(sectionContentSelector).waitFor(); } /** @@ -360,20 +443,31 @@ export default class EditorPage extends BasePage { * * @return {Promise} */ - async openSection( sectionId: string ): Promise { - const sectionSelector = `.elementor-control-${ sectionId }`, - isOpenSection = await this.page.evaluate( ( selector ) => { - const sectionElement = document.querySelector( selector ); - - return sectionElement?.classList.contains( 'e-open' ) || sectionElement?.classList.contains( 'elementor-open' ); - }, sectionSelector ), - section = await this.page.$( sectionSelector + ':not( .e-open ):not( .elementor-open ):visible' ); - - if ( ! section || isOpenSection ) { + async openSection(sectionId: string): Promise { + const sectionSelector = `.elementor-control-${sectionId}`, + isOpenSection = await this.page.evaluate((selector) => { + const sectionElement = document.querySelector(selector); + + return ( + sectionElement?.classList.contains('e-open') || + sectionElement?.classList.contains('elementor-open') + ); + }, sectionSelector), + section = await this.page.$( + sectionSelector + ':not( .e-open ):not( .elementor-open ):visible', + ); + + if (!section || isOpenSection) { return; } - await this.page.locator( sectionSelector + ':not( .e-open ):not( .elementor-open ):visible' + ' .elementor-panel-heading' ).click(); + await this.page + .locator( + sectionSelector + + ':not( .e-open ):not( .elementor-open ):visible' + + ' .elementor-panel-heading', + ) + .click(); } /** @@ -383,20 +477,25 @@ export default class EditorPage extends BasePage { * * @return {Promise} */ - async closeSection( sectionId: string ): Promise { - const sectionSelector = `.elementor-control-${ sectionId }`, - isOpenSection = await this.page.evaluate( ( selector ) => { - const sectionElement = document.querySelector( selector ); + async closeSection(sectionId: string): Promise { + const sectionSelector = `.elementor-control-${sectionId}`, + isOpenSection = await this.page.evaluate((selector) => { + const sectionElement = document.querySelector(selector); - return sectionElement?.classList.contains( 'e-open' ) || sectionElement?.classList.contains( 'elementor-open' ); - }, sectionSelector ), - section = await this.page.$( sectionSelector + '.e-open:visible' ); + return ( + sectionElement?.classList.contains('e-open') || + sectionElement?.classList.contains('elementor-open') + ); + }, sectionSelector), + section = await this.page.$(sectionSelector + '.e-open:visible'); - if ( ! section || ! isOpenSection ) { + if (!section || !isOpenSection) { return; } - await this.page.locator( sectionSelector + '.e-open:visible .elementor-panel-heading' ).click(); + await this.page + .locator(sectionSelector + '.e-open:visible .elementor-panel-heading') + .click(); } /** @@ -406,14 +505,25 @@ export default class EditorPage extends BasePage { * * @return {Promise} */ - async openV2Section( sectionId: 'layout' | 'spacing' | 'size' | 'position' | 'typography' | 'background' | 'border' ) { - const sectionButton = this.page.locator( '.MuiButtonBase-root', { hasText: new RegExp( sectionId, 'i' ) } ); - const contentSelector = await sectionButton.getAttribute( 'aria-controls' ); - const isContentVisible = await this.page.evaluate( ( selector ) => { - return !! document.getElementById( selector ); - }, contentSelector ); - - if ( isContentVisible ) { + async openV2Section( + sectionId: + | 'layout' + | 'spacing' + | 'size' + | 'position' + | 'typography' + | 'background' + | 'border', + ) { + const sectionButton = this.page.locator('.MuiButtonBase-root', { + hasText: new RegExp(sectionId, 'i'), + }); + const contentSelector = await sectionButton.getAttribute('aria-controls'); + const isContentVisible = await this.page.evaluate((selector) => { + return !!document.getElementById(selector); + }, contentSelector); + + if (isContentVisible) { return; } @@ -427,10 +537,10 @@ export default class EditorPage extends BasePage { * * @return {Promise} */ - async setWidgetCustomWidth( width: string = '100' ): Promise { - await this.openPanelTab( 'advanced' ); - await this.setSelectControlValue( '_element_width', 'initial' ); - await this.setSliderControlValue( '_element_custom_width', width ); + async setWidgetCustomWidth(width: string = '100'): Promise { + await this.openPanelTab('advanced'); + await this.setSelectControlValue('_element_width', 'initial'); + await this.setSliderControlValue('_element_custom_width', width); } /** @@ -441,8 +551,11 @@ export default class EditorPage extends BasePage { * * @return {Promise} */ - async setTabControlValue( controlId: string, tabId: string ): Promise { - await this.page.locator( `.elementor-control-${ controlId } .elementor-control-${ tabId }` ).first().click(); + async setTabControlValue(controlId: string, tabId: string): Promise { + await this.page + .locator(`.elementor-control-${controlId} .elementor-control-${tabId}`) + .first() + .click(); } /** @@ -453,8 +566,11 @@ export default class EditorPage extends BasePage { * * @return {Promise} */ - async setTextControlValue( controlId: string, value: string ): Promise { - await this.page.locator( `.elementor-control-${ controlId } input` ).nth( 0 ).fill( value.toString() ); + async setTextControlValue(controlId: string, value: string): Promise { + await this.page + .locator(`.elementor-control-${controlId} input`) + .nth(0) + .fill(value.toString()); } /** @@ -465,8 +581,13 @@ export default class EditorPage extends BasePage { * * @return {Promise} */ - async setTextareaControlValue( controlId: string, value: string ): Promise { - await this.page.locator( `.elementor-control-${ controlId } textarea` ).fill( value.toString() ); + async setTextareaControlValue( + controlId: string, + value: string, + ): Promise { + await this.page + .locator(`.elementor-control-${controlId} textarea`) + .fill(value.toString()); } /** @@ -477,8 +598,10 @@ export default class EditorPage extends BasePage { * * @return {Promise} */ - async setNumberControlValue( controlId: string, value: string ): Promise { - await this.page.locator( `.elementor-control-${ controlId } input >> nth=0` ).fill( value.toString() ); + async setNumberControlValue(controlId: string, value: string): Promise { + await this.page + .locator(`.elementor-control-${controlId} input >> nth=0`) + .fill(value.toString()); } /** @@ -487,8 +610,10 @@ export default class EditorPage extends BasePage { * @param {string} controlId - The control to set the value to. * @param {string} value - The value to set. */ - async setSliderControlValue( controlId: string, value: string ): Promise { - await this.page.locator( `.elementor-control-${ controlId } .elementor-slider-input input` ).fill( value ); + async setSliderControlValue(controlId: string, value: string): Promise { + await this.page + .locator(`.elementor-control-${controlId} .elementor-slider-input input`) + .fill(value); } /** @@ -499,8 +624,11 @@ export default class EditorPage extends BasePage { * * @return {Promise} */ - async setSelectControlValue( controlId: string, value: string ): Promise { - await this.page.selectOption( `.elementor-control-${ controlId } select`, value ); + async setSelectControlValue(controlId: string, value: string): Promise { + await this.page.selectOption( + `.elementor-control-${controlId} select`, + value, + ); } /** @@ -512,17 +640,33 @@ export default class EditorPage extends BasePage { * * @return {Promise} */ - async setSelect2ControlValue( controlId: string, value: string, exactMatch: boolean = true ): Promise { - await this.page.locator( `.elementor-control-${ controlId } .select2:not( .select2-container--disabled )` ).click(); - await this.page.locator( '.select2-search--dropdown input[type="search"]' ).fill( value ); - - if ( exactMatch ) { - await this.page.locator( `.select2-results__option:text-is("${ value }")` ).first().click(); + async setSelect2ControlValue( + controlId: string, + value: string, + exactMatch: boolean = true, + ): Promise { + await this.page + .locator( + `.elementor-control-${controlId} .select2:not( .select2-container--disabled )`, + ) + .click(); + await this.page + .locator('.select2-search--dropdown input[type="search"]') + .fill(value); + + if (exactMatch) { + await this.page + .locator(`.select2-results__option:text-is("${value}")`) + .first() + .click(); } else { - await this.page.locator( `.select2-results__option:has-text("${ value }")` ).first().click(); + await this.page + .locator(`.select2-results__option:has-text("${value}")`) + .first() + .click(); } - await this.page.waitForLoadState( 'domcontentloaded' ); + await this.page.waitForLoadState('domcontentloaded'); } /** @@ -533,8 +677,12 @@ export default class EditorPage extends BasePage { * * @return {Promise} */ - async setDimensionsValue( controlId: string, value: string ): Promise { - await this.page.locator( `.elementor-control-${ controlId } .elementor-control-dimensions li:first-child input` ).fill( value ); + async setDimensionsValue(controlId: string, value: string): Promise { + await this.page + .locator( + `.elementor-control-${controlId} .elementor-control-dimensions li:first-child input`, + ) + .fill(value); } /** @@ -547,8 +695,8 @@ export default class EditorPage extends BasePage { * * @return {Promise} */ - async setChooseControlValue( controlId: string, icon: string ): Promise { - await this.page.locator( `.elementor-control-${ controlId } .${ icon }` ).click(); + async setChooseControlValue(controlId: string, icon: string): Promise { + await this.page.locator(`.elementor-control-${controlId} .${icon}`).click(); } /** @@ -559,12 +707,14 @@ export default class EditorPage extends BasePage { * * @return {Promise} */ - async setColorControlValue( controlId: string, value: string ): Promise { - const controlSelector = `.elementor-control-${ controlId }`; + async setColorControlValue(controlId: string, value: string): Promise { + const controlSelector = `.elementor-control-${controlId}`; - await this.page.locator( controlSelector + ' .pcr-button' ).click(); - await this.page.locator( '.pcr-app.visible .pcr-interaction input.pcr-result' ).fill( value ); - await this.page.locator( controlSelector ).click(); + await this.page.locator(controlSelector + ' .pcr-button').click(); + await this.page + .locator('.pcr-app.visible .pcr-interaction input.pcr-result') + .fill(value); + await this.page.locator(controlSelector).click(); } /** @@ -575,12 +725,19 @@ export default class EditorPage extends BasePage { * * @return {Promise} */ - async setSwitcherControlValue( controlId: string, value: boolean = true ): Promise { - const controlSelector = `.elementor-control-${ controlId }`, - controlLabel = this.page.locator( controlSelector + ' label.elementor-switch' ), - currentState = await this.page.locator( controlSelector + ' input[type="checkbox"]' ).isChecked(); - - if ( currentState !== Boolean( value ) ) { + async setSwitcherControlValue( + controlId: string, + value: boolean = true, + ): Promise { + const controlSelector = `.elementor-control-${controlId}`, + controlLabel = this.page.locator( + controlSelector + ' label.elementor-switch', + ), + currentState = await this.page + .locator(controlSelector + ' input[type="checkbox"]') + .isChecked(); + + if (currentState !== Boolean(value)) { await controlLabel.click(); } } @@ -593,18 +750,28 @@ export default class EditorPage extends BasePage { * * @return {Promise} */ - async setGapControlValue( controlId: string, value: GapControl ): Promise { - const control = this.page.locator( `.elementor-control-${ controlId }` ); - - if ( 'string' === typeof value ) { - await control.locator( '.elementor-control-gap >> nth=0' ).locator( 'input' ).fill( value ); - } else if ( 'object' === typeof value ) { - await control.locator( '.elementor-link-gaps' ).click(); - await control.locator( '.elementor-control-gap input[data-setting="column"]' ).fill( value.column ); - await control.locator( '.elementor-control-gap input[data-setting="row"]' ).fill( value.row ); - if ( value.unit ) { - await control.locator( '.e-units-switcher' ).click(); - await control.locator( `[data-choose="${ value.unit }"]` ).click(); + async setGapControlValue( + controlId: string, + value: GapControl, + ): Promise { + const control = this.page.locator(`.elementor-control-${controlId}`); + + if ('string' === typeof value) { + await control + .locator('.elementor-control-gap >> nth=0') + .locator('input') + .fill(value); + } else if ('object' === typeof value) { + await control.locator('.elementor-link-gaps').click(); + await control + .locator('.elementor-control-gap input[data-setting="column"]') + .fill(value.column); + await control + .locator('.elementor-control-gap input[data-setting="row"]') + .fill(value.row); + if (value.unit) { + await control.locator('.e-units-switcher').click(); + await control.locator(`[data-choose="${value.unit}"]`).click(); } } } @@ -617,11 +784,18 @@ export default class EditorPage extends BasePage { * * @return {Promise} */ - async setMediaControlImageValue( controlId: string, imageTitle: string ): Promise { - await this.page.locator( `.elementor-control-${ controlId } .elementor-control-media__preview` ).click(); - await this.page.getByRole( 'tab', { name: 'Media Library' } ).click(); - await this.page.locator( `[aria-label="${ imageTitle }"]` ).click(); - await this.page.locator( '.button.media-button' ).click(); + async setMediaControlImageValue( + controlId: string, + imageTitle: string, + ): Promise { + await this.page + .locator( + `.elementor-control-${controlId} .elementor-control-media__preview`, + ) + .click(); + await this.page.getByRole('tab', { name: 'Media Library' }).click(); + await this.page.locator(`[aria-label="${imageTitle}"]`).click(); + await this.page.locator('.button.media-button').click(); } /** @@ -632,33 +806,70 @@ export default class EditorPage extends BasePage { * * @return {Promise} */ - async setTypographyControlValue( controlId: string, fontsize: string ): Promise { - const controlSelector = `.elementor-control-${ controlId }_typography .eicon-edit`; - - await this.page.locator( controlSelector ).click(); - await this.setSliderControlValue( controlId + '_font_size', fontsize ); - await this.page.locator( controlSelector ).click(); - } - - async setShadowControlValue( controlId: string, shadowType: string ): Promise { - await this.page.locator( `.elementor-control-${ controlId }_${ shadowType }_shadow_type i.eicon-edit` ).click(); - await this.page.locator( `.elementor-control-${ controlId }_${ shadowType }_shadow_type label` ).first().click(); - } - - async setTextStrokeControlValue( controlId: string, strokeType: string, value: number, color: string ): Promise { - await this.page.locator( `.elementor-control-${ controlId }_${ strokeType }_stroke_type i.eicon-edit` ).click(); - await this.page.locator( `.elementor-control-${ controlId }_${ strokeType }_stroke input[type="number"]` ).first().fill( value.toString() ); - await this.page.locator( `.elementor-control-${ controlId }_stroke_color .pcr-button` ).first().click(); - await this.page.locator( '.pcr-app.visible .pcr-result' ).first().fill( color ); - await this.page.locator( `.elementor-control-${ controlId }_${ strokeType }_stroke_type label` ).first().click(); + async setTypographyControlValue( + controlId: string, + fontsize: string, + ): Promise { + const controlSelector = `.elementor-control-${controlId}_typography .eicon-edit`; + + await this.page.locator(controlSelector).click(); + await this.setSliderControlValue(controlId + '_font_size', fontsize); + await this.page.locator(controlSelector).click(); + } + + async setShadowControlValue( + controlId: string, + shadowType: string, + ): Promise { + await this.page + .locator( + `.elementor-control-${controlId}_${shadowType}_shadow_type i.eicon-edit`, + ) + .click(); + await this.page + .locator( + `.elementor-control-${controlId}_${shadowType}_shadow_type label`, + ) + .first() + .click(); + } + + async setTextStrokeControlValue( + controlId: string, + strokeType: string, + value: number, + color: string, + ): Promise { + await this.page + .locator( + `.elementor-control-${controlId}_${strokeType}_stroke_type i.eicon-edit`, + ) + .click(); + await this.page + .locator( + `.elementor-control-${controlId}_${strokeType}_stroke input[type="number"]`, + ) + .first() + .fill(value.toString()); + await this.page + .locator(`.elementor-control-${controlId}_stroke_color .pcr-button`) + .first() + .click(); + await this.page.locator('.pcr-app.visible .pcr-result').first().fill(color); + await this.page + .locator( + `.elementor-control-${controlId}_${strokeType}_stroke_type label`, + ) + .first() + .click(); } async setWidgetMask(): Promise { - await this.openSection( '_section_masking' ); - await this.setSwitcherControlValue( '_mask_switch', true ); - await this.setSelectControlValue( '_mask_size', 'custom' ); - await this.setSliderControlValue( '_mask_size_scale', '30' ); - await this.setSelectControlValue( '_mask_position', 'top right' ); + await this.openSection('_section_masking'); + await this.setSwitcherControlValue('_mask_switch', true); + await this.setSelectControlValue('_mask_size', 'custom'); + await this.setSliderControlValue('_mask_size_scale', '30'); + await this.setSelectControlValue('_mask_position', 'top right'); } /** @@ -667,18 +878,20 @@ export default class EditorPage extends BasePage { * @return {Promise} */ async hideVideoControls(): Promise { - await this.getPreviewFrame().waitForSelector( '.elementor-video' ); + await this.getPreviewFrame().waitForSelector('.elementor-video'); - const videoFrame = this.getPreviewFrame().frameLocator( '.elementor-video' ), - videoButton = videoFrame.locator( 'button.ytp-large-play-button.ytp-button.ytp-large-play-button-red-bg' ), - videoGradient = videoFrame.locator( '.ytp-gradient-top' ), - videoTitle = videoFrame.locator( '.ytp-show-cards-title' ), - videoBottom = videoFrame.locator( '.ytp-impression-link' ); + const videoFrame = this.getPreviewFrame().frameLocator('.elementor-video'), + videoButton = videoFrame.locator( + 'button.ytp-large-play-button.ytp-button.ytp-large-play-button-red-bg', + ), + videoGradient = videoFrame.locator('.ytp-gradient-top'), + videoTitle = videoFrame.locator('.ytp-show-cards-title'), + videoBottom = videoFrame.locator('.ytp-impression-link'); - await videoButton.evaluate( ( element ) => element.style.opacity = '0' ); - await videoGradient.evaluate( ( element ) => element.style.opacity = '0' ); - await videoTitle.evaluate( ( element ) => element.style.opacity = '0' ); - await videoBottom.evaluate( ( element ) => element.style.opacity = '0' ); + await videoButton.evaluate((element) => (element.style.opacity = '0')); + await videoGradient.evaluate((element) => (element.style.opacity = '0')); + await videoTitle.evaluate((element) => (element.style.opacity = '0')); + await videoBottom.evaluate((element) => (element.style.opacity = '0')); } /** @@ -687,16 +900,22 @@ export default class EditorPage extends BasePage { * @return {Promise} */ async hideMapControls(): Promise { - await this.getPreviewFrame().waitForSelector( '.elementor-widget-google_maps iframe' ); + await this.getPreviewFrame().waitForSelector( + '.elementor-widget-google_maps iframe', + ); - const mapFrame = this.getPreviewFrame().frameLocator( '.elementor-widget-google_maps iframe' ), - mapText = mapFrame.locator( '.gm-style iframe + div + div' ), - mapInset = mapFrame.locator( 'button.gm-inset-map.gm-inset-light' ), - mapControls = mapFrame.locator( '.gmnoprint.gm-bundled-control.gm-bundled-control-on-bottom' ); + const mapFrame = this.getPreviewFrame().frameLocator( + '.elementor-widget-google_maps iframe', + ), + mapText = mapFrame.locator('.gm-style iframe + div + div'), + mapInset = mapFrame.locator('button.gm-inset-map.gm-inset-light'), + mapControls = mapFrame.locator( + '.gmnoprint.gm-bundled-control.gm-bundled-control-on-bottom', + ); - await mapText.evaluate( ( element ) => element.style.opacity = '0' ); - await mapInset.evaluate( ( element ) => element.style.opacity = '0' ); - await mapControls.evaluate( ( element ) => element.style.opacity = '0' ); + await mapText.evaluate((element) => (element.style.opacity = '0')); + await mapInset.evaluate((element) => (element.style.opacity = '0')); + await mapControls.evaluate((element) => (element.style.opacity = '0')); } /** @@ -705,13 +924,13 @@ export default class EditorPage extends BasePage { * @return {Promise} */ async togglePreviewMode(): Promise { - if ( ! await this.page.$( 'body.elementor-editor-preview' ) ) { - await this.page.locator( '#elementor-mode-switcher' ).click(); - await this.page.waitForSelector( 'body.elementor-editor-preview' ); - await this.page.waitForTimeout( 500 ); + if (!(await this.page.$('body.elementor-editor-preview'))) { + await this.page.locator('#elementor-mode-switcher').click(); + await this.page.waitForSelector('body.elementor-editor-preview'); + await this.page.waitForTimeout(500); } else { - await this.page.locator( '#elementor-mode-switcher-preview' ).click(); - await this.page.waitForSelector( 'body.elementor-editor-active' ); + await this.page.locator('#elementor-mode-switcher-preview').click(); + await this.page.waitForSelector('body.elementor-editor-active'); } } @@ -721,8 +940,10 @@ export default class EditorPage extends BasePage { * @return {Promise} */ async waitForPreviewToLoad(): Promise { - await this.page.waitForSelector( '#elementor-preview-loading' ); - await this.page.waitForSelector( '#elementor-preview-loading', { state: 'hidden' } ); + await this.page.waitForSelector('#elementor-preview-loading'); + await this.page.waitForSelector('#elementor-preview-loading', { + state: 'hidden', + }); } /** @@ -731,10 +952,11 @@ export default class EditorPage extends BasePage { * @return {Promise} */ async hideEditorElements(): Promise { - const css = ''; + const css = + ''; - await this.addWidget( 'html' ); - await this.setTextareaControlValue( 'type-code', css ); + await this.addWidget('html'); + await this.setTextareaControlValue('type-code', css); } /** @@ -743,7 +965,9 @@ export default class EditorPage extends BasePage { * @return {Promise} Returns true if the Top Bar is visible, false otherwise. */ async hasTopBar(): Promise { - return await this.page.locator( EditorSelectors.panels.topBar.wrapper ).isVisible(); + return await this.page + .locator(EditorSelectors.panels.topBar.wrapper) + .isVisible(); } /** @@ -753,12 +977,18 @@ export default class EditorPage extends BasePage { * * @return {Promise} */ - async clickTopBarItem( selector: TopBarSelector ): Promise { - const topbarLocator = this.page.locator( EditorSelectors.panels.topBar.wrapper ); - if ( 'text' === selector.attribute ) { - await topbarLocator.getByRole( 'button', { name: selector.attributeValue } ).click(); + async clickTopBarItem(selector: TopBarSelector): Promise { + const topbarLocator = this.page.locator( + EditorSelectors.panels.topBar.wrapper, + ); + if ('text' === selector.attribute) { + await topbarLocator + .getByRole('button', { name: selector.attributeValue }) + .click(); } else { - await topbarLocator.locator( `button[${ selector.attribute }="${ selector.attributeValue }"]` ).click(); + await topbarLocator + .locator(`button[${selector.attribute}="${selector.attributeValue}"]`) + .click(); } } @@ -771,12 +1001,14 @@ export default class EditorPage extends BasePage { * * @return {Promise} */ - async openMenuPanel( innerPanel?: string ): Promise { - await this.page.locator( EditorSelectors.panels.menu.footerButton ).click(); - await this.page.locator( EditorSelectors.panels.menu.wrapper ).waitFor(); + async openMenuPanel(innerPanel?: string): Promise { + await this.page.locator(EditorSelectors.panels.menu.footerButton).click(); + await this.page.locator(EditorSelectors.panels.menu.wrapper).waitFor(); - if ( innerPanel ) { - await this.page.locator( `.elementor-panel-menu-item-${ innerPanel }` ).click(); + if (innerPanel) { + await this.page + .locator(`.elementor-panel-menu-item-${innerPanel}`) + .click(); } } @@ -788,13 +1020,15 @@ export default class EditorPage extends BasePage { async openElementsPanel(): Promise { const hasTopBar = await this.hasTopBar(); - if ( hasTopBar ) { - await this.clickTopBarItem( TopBarSelectors.elementsPanel ); + if (hasTopBar) { + await this.clickTopBarItem(TopBarSelectors.elementsPanel); } else { - await this.page.locator( EditorSelectors.panels.elements.footerButton ).click(); + await this.page + .locator(EditorSelectors.panels.elements.footerButton) + .click(); } - await this.page.locator( EditorSelectors.panels.elements.wrapper ).waitFor(); + await this.page.locator(EditorSelectors.panels.elements.wrapper).waitFor(); } /** @@ -805,13 +1039,17 @@ export default class EditorPage extends BasePage { async openPageSettingsPanel(): Promise { const hasTopBar = await this.hasTopBar(); - if ( hasTopBar ) { - await this.clickTopBarItem( TopBarSelectors.documentSettings ); + if (hasTopBar) { + await this.clickTopBarItem(TopBarSelectors.documentSettings); } else { - await this.page.locator( EditorSelectors.panels.pageSettings.footerButton ).click(); + await this.page + .locator(EditorSelectors.panels.pageSettings.footerButton) + .click(); } - await this.page.locator( EditorSelectors.panels.pageSettings.wrapper ).waitFor(); + await this.page + .locator(EditorSelectors.panels.pageSettings.wrapper) + .waitFor(); } /** @@ -821,19 +1059,23 @@ export default class EditorPage extends BasePage { * * @return {Promise} */ - async openSiteSettings( innerPanel?: string ): Promise { + async openSiteSettings(innerPanel?: string): Promise { const hasTopBar = await this.hasTopBar(); - if ( hasTopBar ) { - await this.clickTopBarItem( TopBarSelectors.siteSettings ); + if (hasTopBar) { + await this.clickTopBarItem(TopBarSelectors.siteSettings); } else { - await this.openMenuPanel( 'global-settings' ); + await this.openMenuPanel('global-settings'); } - await this.page.locator( EditorSelectors.panels.siteSettings.wrapper ).waitFor(); + await this.page + .locator(EditorSelectors.panels.siteSettings.wrapper) + .waitFor(); - if ( innerPanel ) { - await this.page.locator( `.elementor-panel-menu-item-${ innerPanel }` ).click(); + if (innerPanel) { + await this.page + .locator(`.elementor-panel-menu-item-${innerPanel}`) + .click(); } } @@ -845,15 +1087,19 @@ export default class EditorPage extends BasePage { async openUserPreferencesPanel(): Promise { const hasTopBar = await this.hasTopBar(); - if ( hasTopBar ) { - await this.clickTopBarItem( TopBarSelectors.elementorLogo ); - await this.page.waitForTimeout( 100 ); - await this.page.getByRole( 'menuitem', { name: 'User Preferences' } ).click(); + if (hasTopBar) { + await this.clickTopBarItem(TopBarSelectors.elementorLogo); + await this.page.waitForTimeout(100); + await this.page + .getByRole('menuitem', { name: 'User Preferences' }) + .click(); } else { - await this.openMenuPanel( 'editor-preferences' ); + await this.openMenuPanel('editor-preferences'); } - await this.page.locator( EditorSelectors.panels.userPreferences.wrapper ).waitFor(); + await this.page + .locator(EditorSelectors.panels.userPreferences.wrapper) + .waitFor(); } /** @@ -863,13 +1109,17 @@ export default class EditorPage extends BasePage { */ async closeNavigatorIfOpen(): Promise { await this.waitForPreviewFrame(); - const isOpen = await this.getPreviewFrame().evaluate( () => elementor.navigator.isOpen() ); + const isOpen = await this.getPreviewFrame().evaluate(() => + elementor.navigator.isOpen(), + ); - if ( ! isOpen ) { + if (!isOpen) { return; } - await this.page.locator( EditorSelectors.panels.navigator.closeButton ).click(); + await this.page + .locator(EditorSelectors.panels.navigator.closeButton) + .click(); } /** @@ -879,11 +1129,13 @@ export default class EditorPage extends BasePage { * * @return {Promise} */ - async setPageTemplate( template: 'default' | 'canvas' | 'full-width' ): Promise { + async setPageTemplate( + template: 'default' | 'canvas' | 'full-width', + ): Promise { let templateValue: string; let templateClass: string; - switch ( template ) { + switch (template) { case 'default': templateValue = 'default'; templateClass = '.elementor-default'; @@ -899,14 +1151,14 @@ export default class EditorPage extends BasePage { } // Check if the template is already set - if ( await this.getPreviewFrame().$( templateClass ) ) { + if (await this.getPreviewFrame().$(templateClass)) { return; } // Select the template await this.openPageSettingsPanel(); - await this.setSelectControlValue( 'template', templateValue ); - await this.getPreviewFrame().waitForSelector( templateClass ); + await this.setSelectControlValue('template', templateValue); + await this.getPreviewFrame().waitForSelector(templateClass); } /** @@ -916,7 +1168,7 @@ export default class EditorPage extends BasePage { * * @return {Promise} */ - async setDisplayMode( uiMode: string ): Promise { + async setDisplayMode(uiMode: string): Promise { const uiThemeOptions = { light: 'eicon-light-mode', dark: 'eicon-dark-mode', @@ -924,7 +1176,7 @@ export default class EditorPage extends BasePage { }; await this.openUserPreferencesPanel(); - await this.setChooseControlValue( 'ui_theme', uiThemeOptions[ uiMode ] ); + await this.setChooseControlValue('ui_theme', uiThemeOptions[uiMode]); } /** @@ -935,10 +1187,12 @@ export default class EditorPage extends BasePage { * @return {Promise} */ async openResponsiveViewBar(): Promise { - const hasResponsiveViewBar = await this.page.evaluate( () => elementor.isDeviceModeActive() ); + const hasResponsiveViewBar = await this.page.evaluate(() => + elementor.isDeviceModeActive(), + ); - if ( ! hasResponsiveViewBar ) { - await this.page.locator( '#elementor-panel-footer-responsive i' ).click(); + if (!hasResponsiveViewBar) { + await this.page.locator('#elementor-panel-footer-responsive i').click(); } } @@ -949,13 +1203,17 @@ export default class EditorPage extends BasePage { * * @return {Promise} */ - async changeResponsiveView( device: Device ): Promise { + async changeResponsiveView(device: Device): Promise { const hasTopBar = await this.hasTopBar(); - if ( hasTopBar ) { - await Breakpoints.getDeviceLocator( this.page, device ).click(); + if (hasTopBar) { + await Breakpoints.getDeviceLocator(this.page, device).click(); } else { await this.openResponsiveViewBar(); - await this.page.locator( `#e-responsive-bar-switcher__option-${ device }` ).first().locator( 'i' ).click(); + await this.page + .locator(`#e-responsive-bar-switcher__option-${device}`) + .first() + .locator('i') + .click(); } } @@ -967,14 +1225,20 @@ export default class EditorPage extends BasePage { async publishPage(): Promise { const hasTopBar = await this.hasTopBar(); - if ( hasTopBar ) { - await this.clickTopBarItem( TopBarSelectors.publish ); + if (hasTopBar) { + await this.clickTopBarItem(TopBarSelectors.publish); await this.page.waitForLoadState(); - await this.page.locator( EditorSelectors.panels.topBar.wrapper + ' button[disabled]', { hasText: 'Publish' } ).waitFor( { timeout: timeouts.longAction } ); + await this.page + .locator(EditorSelectors.panels.topBar.wrapper + ' button[disabled]', { + hasText: 'Publish', + }) + .waitFor({ timeout: timeouts.longAction }); } else { - await this.page.locator( 'button#elementor-panel-saver-button-publish' ).click(); + await this.page + .locator('button#elementor-panel-saver-button-publish') + .click(); await this.page.waitForLoadState(); - await this.page.getByRole( 'button', { name: 'Update' } ).waitFor(); + await this.page.getByRole('button', { name: 'Update' }).waitFor(); } } @@ -988,13 +1252,13 @@ export default class EditorPage extends BasePage { await this.publishPage(); - if ( hasTopBar ) { - await this.clickTopBarItem( TopBarSelectors.saveOptions ); - await this.page.getByRole( 'menuitem', { name: 'View Page' } ).click(); + if (hasTopBar) { + await this.clickTopBarItem(TopBarSelectors.saveOptions); + await this.page.getByRole('menuitem', { name: 'View Page' }).click(); const pageId = await this.getPageId(); - await this.page.goto( `/?p=${ pageId }` ); + await this.page.goto(`/?p=${pageId}`); } else { - await this.openMenuPanel( 'view-page' ); + await this.openMenuPanel('view-page'); } await this.page.waitForLoadState(); @@ -1002,7 +1266,7 @@ export default class EditorPage extends BasePage { async viewPage() { const pageId = await this.getPageId(); - await this.page.goto( `/?p=${ pageId }` ); + await this.page.goto(`/?p=${pageId}`); await this.page.waitForLoadState(); } @@ -1014,14 +1278,14 @@ export default class EditorPage extends BasePage { async saveAndReloadPage(): Promise { const hasTopBar = await this.hasTopBar(); - if ( hasTopBar ) { - await this.clickTopBarItem( TopBarSelectors.publish ); + if (hasTopBar) { + await this.clickTopBarItem(TopBarSelectors.publish); } else { - await this.page.locator( '#elementor-panel-saver-button-publish' ).click(); + await this.page.locator('#elementor-panel-saver-button-publish').click(); } await this.page.waitForLoadState(); - await this.page.waitForResponse( '/wp-admin/admin-ajax.php' ); + await this.page.waitForResponse('/wp-admin/admin-ajax.php'); await this.page.reload(); } @@ -1031,7 +1295,7 @@ export default class EditorPage extends BasePage { * @return {Promise} The ID of the current page. */ async getPageId(): Promise { - return await this.page.evaluate( () => elementor.config.initialDocument.id ); + return await this.page.evaluate(() => elementor.config.initialDocument.id); } /** @@ -1056,12 +1320,16 @@ export default class EditorPage extends BasePage { * * @return {Promise} */ - async applyElementSettings( elementId: string, settings: unknown ): Promise { + async applyElementSettings( + elementId: string, + settings: unknown, + ): Promise { await this.page.evaluate( - ( args ) => $e.run( 'document/elements/settings', { - container: elementor.getContainer( args.elementId ), - settings: args.settings, - } ), + (args) => + $e.run('document/elements/settings', { + container: elementor.getContainer(args.elementId), + settings: args.settings, + }), { elementId, settings }, ); } @@ -1073,26 +1341,32 @@ export default class EditorPage extends BasePage { * * @return {Promise} Returns true if the item is in the viewport, false otherwise. */ - async isItemInViewport( itemSelector: string ): Promise { - return this.page.evaluate( ( item: string ) => { + async isItemInViewport(itemSelector: string): Promise { + return this.page.evaluate((item: string) => { let isVisible = false; - const element: HTMLElement = document.querySelector( item ); + const element: HTMLElement = document.querySelector(item); - if ( element ) { + if (element) { const rect = element.getBoundingClientRect(); - if ( rect.top >= 0 && rect.left >= 0 ) { - const vw = Math.max( document.documentElement.clientWidth || 0, window.innerWidth || 0 ), - vh = Math.max( document.documentElement.clientHeight || 0, window.innerHeight || 0 ); - - if ( rect.right <= vw && rect.bottom <= vh ) { + if (rect.top >= 0 && rect.left >= 0) { + const vw = Math.max( + document.documentElement.clientWidth || 0, + window.innerWidth || 0, + ), + vh = Math.max( + document.documentElement.clientHeight || 0, + window.innerHeight || 0, + ); + + if (rect.right <= vw && rect.bottom <= vh) { isVisible = true; } } } return isVisible; - }, itemSelector ); + }, itemSelector); } /** @@ -1101,7 +1375,7 @@ export default class EditorPage extends BasePage { * @return {Promise} The number of widgets in the editor. */ async getWidgetCount(): Promise { - return ( await this.getPreviewFrame().$$( EditorSelectors.widget ) ).length; + return (await this.getPreviewFrame().$$(EditorSelectors.widget)).length; } /** @@ -1112,33 +1386,54 @@ export default class EditorPage extends BasePage { * * @return {Promise} */ - async waitForIframeToLoaded( widgetType: string, isPublished: boolean = false ): Promise { + async waitForIframeToLoaded( + widgetType: string, + isPublished: boolean = false, + ): Promise { const frames = { - video: [ EditorSelectors.video.iframe, EditorSelectors.video.playIcon ], - google_maps: [ EditorSelectors.googleMaps.iframe, EditorSelectors.googleMaps.showSatelliteViewBtn ], - sound_cloud: [ EditorSelectors.soundCloud.iframe, EditorSelectors.soundCloud.waveForm ], + video: [EditorSelectors.video.iframe, EditorSelectors.video.playIcon], + google_maps: [ + EditorSelectors.googleMaps.iframe, + EditorSelectors.googleMaps.showSatelliteViewBtn, + ], + sound_cloud: [ + EditorSelectors.soundCloud.iframe, + EditorSelectors.soundCloud.waveForm, + ], }; - if ( ! ( widgetType in frames ) ) { + if (!(widgetType in frames)) { return; } - if ( isPublished ) { - await this.page.locator( frames[ widgetType ][ 0 ] ).first().waitFor(); - const count = await this.page.locator( frames[ widgetType ][ 0 ] ).count(); - for ( let i = 1; i < count; i++ ) { - await this.page.frameLocator( frames[ widgetType ][ 0 ] ).nth( i ).locator( frames[ widgetType ][ 1 ] ).waitFor(); + if (isPublished) { + await this.page.locator(frames[widgetType][0]).first().waitFor(); + const count = await this.page.locator(frames[widgetType][0]).count(); + for (let i = 1; i < count; i++) { + await this.page + .frameLocator(frames[widgetType][0]) + .nth(i) + .locator(frames[widgetType][1]) + .waitFor(); } } else { const frame = this.getPreviewFrame(); await frame.waitForLoadState(); - await frame.waitForSelector( frames[ widgetType ][ 0 ] ); - await frame.frameLocator( frames[ widgetType ][ 0 ] ).first().locator( frames[ widgetType ][ 1 ] ).waitFor(); - const iframeCount: number = await new Promise( ( resolved ) => { - resolved( frame.childFrames().length ); - } ); - for ( let i = 1; i < iframeCount; i++ ) { - await frame.frameLocator( frames[ widgetType ][ 0 ] ).nth( i ).locator( frames[ widgetType ][ 1 ] ).waitFor(); + await frame.waitForSelector(frames[widgetType][0]); + await frame + .frameLocator(frames[widgetType][0]) + .first() + .locator(frames[widgetType][1]) + .waitFor(); + const iframeCount: number = await new Promise((resolved) => { + resolved(frame.childFrames().length); + }); + for (let i = 1; i < iframeCount; i++) { + await frame + .frameLocator(frames[widgetType][0]) + .nth(i) + .locator(frames[widgetType][1]) + .waitFor(); } } } @@ -1151,17 +1446,17 @@ export default class EditorPage extends BasePage { * * @return {Promise} */ - async waitForElement( isPublished: boolean, selector: string ): Promise { - if ( selector === undefined ) { + async waitForElement(isPublished: boolean, selector: string): Promise { + if (selector === undefined) { return; } - if ( isPublished ) { - await this.page.waitForSelector( selector ); + if (isPublished) { + await this.page.waitForSelector(selector); } else { const frame = this.getPreviewFrame(); await frame.waitForLoadState(); - await frame.waitForSelector( selector ); + await frame.waitForSelector(selector); } } @@ -1175,12 +1470,18 @@ export default class EditorPage extends BasePage { * * @return {Promise} */ - async verifyClassInElement( args: { selector: string, className: string, isPublished: boolean } ): Promise { - const regex = new RegExp( args.className ); - if ( args.isPublished ) { - await expect( this.page.locator( args.selector ) ).toHaveClass( regex ); + async verifyClassInElement(args: { + selector: string; + className: string; + isPublished: boolean; + }): Promise { + const regex = new RegExp(args.className); + if (args.isPublished) { + await expect(this.page.locator(args.selector)).toHaveClass(regex); } else { - await expect( this.getPreviewFrame().locator( args.selector ) ).toHaveClass( regex ); + await expect(this.getPreviewFrame().locator(args.selector)).toHaveClass( + regex, + ); } } @@ -1195,12 +1496,17 @@ export default class EditorPage extends BasePage { * * @return {Promise} */ - async verifyImageSize( args: { selector: string, width: number, height: number, isPublished: boolean } ): Promise { + async verifyImageSize(args: { + selector: string; + width: number; + height: number; + isPublished: boolean; + }): Promise { const imageSize = args.isPublished - ? await this.page.locator( args.selector ).boundingBox() - : await this.getPreviewFrame().locator( args.selector ).boundingBox(); - expect( imageSize.width ).toEqual( args.width ); - expect( imageSize.height ).toEqual( args.height ); + ? await this.page.locator(args.selector).boundingBox() + : await this.getPreviewFrame().locator(args.selector).boundingBox(); + expect(imageSize.width).toEqual(args.width); + expect(imageSize.height).toEqual(args.height); } /** @@ -1214,8 +1520,12 @@ export default class EditorPage extends BasePage { * * @return {Promise} */ - async isUiStable( locator: Locator, retries: number = 3, timeout: number = 500 ): Promise { - const comparator = getComparator( 'image/png' ); + async isUiStable( + locator: Locator, + retries: number = 3, + timeout: number = 500, + ): Promise { + const comparator = getComparator('image/png'); let retry = 0, beforeImage: Buffer, afterImage: Buffer; @@ -1223,21 +1533,21 @@ export default class EditorPage extends BasePage { await locator.waitFor(); do { - if ( retry === retries ) { + if (retry === retries) { break; } - beforeImage = await locator.screenshot( { + beforeImage = await locator.screenshot({ path: `./before.png`, - } ); + }); - await new Promise( ( resolve ) => setTimeout( resolve, timeout ) ); + await new Promise((resolve) => setTimeout(resolve, timeout)); - afterImage = await locator.screenshot( { + afterImage = await locator.screenshot({ path: `./after.png`, - } ); + }); retry = retry++; - } while ( null !== comparator( beforeImage, afterImage ) ); + } while (null !== comparator(beforeImage, afterImage)); } /** @@ -1247,17 +1557,17 @@ export default class EditorPage extends BasePage { * * @return {Promise} */ - async removeClasses( className: string ): Promise { - await this.page.evaluate( async ( _class ) => { - await new Promise( ( resolve1 ) => { - const elems = document.querySelectorAll( `.${ _class }` ); + async removeClasses(className: string): Promise { + await this.page.evaluate(async (_class) => { + await new Promise((resolve1) => { + const elems = document.querySelectorAll(`.${_class}`); - [].forEach.call( elems, function( el: HTMLElement ) { - el.classList.remove( _class ); - } ); - resolve1( 'Foo' ); - } ); - }, className ); + [].forEach.call(elems, function (el: HTMLElement) { + el.classList.remove(_class); + }); + resolve1('Foo'); + }); + }, className); } /** @@ -1266,22 +1576,22 @@ export default class EditorPage extends BasePage { * @return {Promise} */ async scrollPage(): Promise { - await this.page.evaluate( async () => { - await new Promise( ( resolve1 ) => { + await this.page.evaluate(async () => { + await new Promise((resolve1) => { let totalHeight = 0; const distance = 400; - const timer = setInterval( () => { + const timer = setInterval(() => { const scrollHeight = document.body.scrollHeight; - window.scrollBy( 0, distance ); + window.scrollBy(0, distance); totalHeight += distance; - if ( totalHeight >= scrollHeight ) { - clearInterval( timer ); - window.scrollTo( 0, 0 ); - resolve1( 'Foo' ); + if (totalHeight >= scrollHeight) { + clearInterval(timer); + window.scrollTo(0, 0); + resolve1('Foo'); } - }, 100 ); - } ); - } ); + }, 100); + }); + }); } /** @@ -1291,11 +1601,13 @@ export default class EditorPage extends BasePage { */ async removeWpAdminBar(): Promise { const adminBar = 'wpadminbar'; - await this.page.locator( `#${ adminBar }` ).waitFor( { timeout: timeouts.longAction } ); - await this.page.evaluate( ( selector ) => { - const admin = document.getElementById( selector ); + await this.page + .locator(`#${adminBar}`) + .waitFor({ timeout: timeouts.longAction }); + await this.page.evaluate((selector) => { + const admin = document.getElementById(selector); admin.remove(); - }, adminBar ); + }, adminBar); } /** @@ -1306,38 +1618,53 @@ export default class EditorPage extends BasePage { * * @return {Promise} The numeric part of the ID with the prefix removed. */ - async isolatedIdNumber( idPrefix: string, itemID: string ): Promise { - return Number( itemID.replace( idPrefix, '' ) ); + async isolatedIdNumber(idPrefix: string, itemID: string): Promise { + return Number(itemID.replace(idPrefix, '')); } - async addImagesToGalleryControl( args?: { images?: string[], metaData?: boolean } ) { - const defaultImages = [ 'A.jpg', 'B.jpg', 'C.jpg', 'D.jpg', 'E.jpg' ]; + async addImagesToGalleryControl(args?: { + images?: string[]; + metaData?: boolean; + }) { + const defaultImages = ['A.jpg', 'B.jpg', 'C.jpg', 'D.jpg', 'E.jpg']; - await this.page.locator( EditorSelectors.galleryControl.addGalleryBtn ).nth( 0 ).click(); - await this.page.getByRole( 'tab', { name: 'Media Library' } ).click(); + await this.page + .locator(EditorSelectors.galleryControl.addGalleryBtn) + .nth(0) + .click(); + await this.page.getByRole('tab', { name: 'Media Library' }).click(); const _images = args?.images === undefined ? defaultImages : args.images; - for ( const i in _images ) { - await this.page.setInputFiles( EditorSelectors.media.imageInp, pathResolve( __dirname, `../resources/${ _images[ i ] }` ) ); + for (const i in _images) { + await this.page.setInputFiles( + EditorSelectors.media.imageInp, + pathResolve(__dirname, `../resources/${_images[i]}`), + ); - if ( args?.metaData ) { + if (args?.metaData) { await this.addTestImageMetaData(); } } - await this.page.locator( EditorSelectors.media.addGalleryButton ).click(); - await this.page.locator( 'text=Insert gallery' ).click(); + await this.page.locator(EditorSelectors.media.addGalleryButton).click(); + await this.page.locator('text=Insert gallery').click(); } - async addTestImageMetaData( args = { caption: 'Test caption!', description: 'Test description!' } ) { - await this.page.locator( EditorSelectors.media.images ).first().click(); - await this.page.locator( EditorSelectors.media.imgCaption ).clear(); - await this.page.locator( EditorSelectors.media.imgCaption ).type( args.caption ); + async addTestImageMetaData( + args = { caption: 'Test caption!', description: 'Test description!' }, + ) { + await this.page.locator(EditorSelectors.media.images).first().click(); + await this.page.locator(EditorSelectors.media.imgCaption).clear(); + await this.page + .locator(EditorSelectors.media.imgCaption) + .type(args.caption); - await this.page.locator( EditorSelectors.media.images ).first().click(); - await this.page.locator( EditorSelectors.media.imgDescription ).clear(); - await this.page.locator( EditorSelectors.media.imgDescription ).type( args.description ); + await this.page.locator(EditorSelectors.media.images).first().click(); + await this.page.locator(EditorSelectors.media.imgDescription).clear(); + await this.page + .locator(EditorSelectors.media.imgDescription) + .type(args.description); } /** @@ -1349,18 +1676,31 @@ export default class EditorPage extends BasePage { * * @return {Promise} */ - async saveSiteSettingsWithTopBar( toReload: boolean ): Promise { - if ( await this.page.locator( EditorSelectors.panels.siteSettings.saveButton ).isEnabled() ) { - await this.page.locator( EditorSelectors.panels.siteSettings.saveButton ).click(); + async saveSiteSettingsWithTopBar(toReload: boolean): Promise { + if ( + await this.page + .locator(EditorSelectors.panels.siteSettings.saveButton) + .isEnabled() + ) { + await this.page + .locator(EditorSelectors.panels.siteSettings.saveButton) + .click(); } else { - await this.page.evaluate( ( selector ) => { - const button: HTMLElement = document.evaluate( selector, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE ).singleNodeValue as HTMLElement; + await this.page.evaluate((selector) => { + const button: HTMLElement = document.evaluate( + selector, + document, + null, + XPathResult.FIRST_ORDERED_NODE_TYPE, + ).singleNodeValue as HTMLElement; button.click(); - }, EditorSelectors.panels.siteSettings.saveButton ); + }, EditorSelectors.panels.siteSettings.saveButton); } - if ( toReload ) { - await this.page.locator( EditorSelectors.refreshPopup.reloadButton ).click(); + if (toReload) { + await this.page + .locator(EditorSelectors.refreshPopup.reloadButton) + .click(); } } @@ -1372,15 +1712,23 @@ export default class EditorPage extends BasePage { * @return {Promise} */ async saveSiteSettingsNoTopBar(): Promise { - await this.page.locator( EditorSelectors.panels.footerTools.updateButton ).click(); - await this.page.locator( EditorSelectors.toast ).waitFor(); + await this.page + .locator(EditorSelectors.panels.footerTools.updateButton) + .click(); + await this.page.locator(EditorSelectors.toast).waitFor(); } - async assertCorrectVwWidthStylingOfElement( element: Locator, vwValue: number = 100 ): Promise { + async assertCorrectVwWidthStylingOfElement( + element: Locator, + vwValue: number = 100, + ): Promise { const viewport = this.page.viewportSize(); - const vwConvertedToPxUnit = viewport.width * vwValue / 100; - const elementWidthInPxUnit = await element.boundingBox().then( ( box ) => box?.width ?? 0 ); - const vwAndPxValuesAreEqual = Math.abs( vwConvertedToPxUnit - elementWidthInPxUnit ) <= 1; - expect( vwAndPxValuesAreEqual ).toBeTruthy(); + const vwConvertedToPxUnit = (viewport.width * vwValue) / 100; + const elementWidthInPxUnit = await element + .boundingBox() + .then((box) => box?.width ?? 0); + const vwAndPxValuesAreEqual = + Math.abs(vwConvertedToPxUnit - elementWidthInPxUnit) <= 1; + expect(vwAndPxValuesAreEqual).toBeTruthy(); } } diff --git a/tests/playwright/pages/settings-page.ts b/tests/playwright/pages/settings-page.ts index 7f50eb59..f2e500b9 100644 --- a/tests/playwright/pages/settings-page.ts +++ b/tests/playwright/pages/settings-page.ts @@ -1,18 +1,20 @@ -import { APIRequestContext, type Page, type TestInfo, expect } from '@playwright/test'; +import { type APIRequestContext, expect } from '@playwright/test'; import WpAdminPage from './wp-admin-page.ts'; export default class SettingsPage extends WpAdminPage { async gotoSettingsPage(): Promise { - await this.page.goto( '/wp-admin/admin.php?page=hello-elementor-settings' ); - await this.page.waitForSelector( 'h4:has-text("Advanced theme settings")', { timeout: 10000 } ); + await this.page.goto('/wp-admin/admin.php?page=hello-elementor-settings'); + await this.page.waitForSelector('h4:has-text("Advanced theme settings")', { + timeout: 10000, + }); } - getTab( tabName: string ) { - return this.page.getByRole( 'tab', { name: tabName } ); + getTab(tabName: string) { + return this.page.getByRole('tab', { name: tabName }); } - getTabPanel( tabName: string ) { - return this.page.getByRole( 'tabpanel', { name: tabName } ); + getTabPanel(tabName: string) { + return this.page.getByRole('tabpanel', { name: tabName }); } async createNewBasicPost(): Promise { @@ -21,28 +23,31 @@ export default class SettingsPage extends WpAdminPage { title: 'Playwright Test Page - Uninitialized', content: 'This is a test content for the post.', status: 'publish' as const, - excerpt: 'This is a test excerpt that should appear in meta description.', + excerpt: + 'This is a test excerpt that should appear in meta description.', }, - postId = await this.apiRequests.create( request, 'pages', postDataInitial ), + postId = await this.apiRequests.create(request, 'pages', postDataInitial), postDataUpdated = { - title: `Playwright Test Page #${ postId }`, + title: `Playwright Test Page #${postId}`, }; - await this.apiRequests.create( request, `pages/${ postId }`, postDataUpdated ); + await this.apiRequests.create(request, `pages/${postId}`, postDataUpdated); return postId; } - async clickTab( tabName: string ): Promise { - await this.getTab( tabName ).click(); - await this.page.waitForSelector( `[role="tabpanel"]:visible`, { timeout: 5000 } ); + async clickTab(tabName: string): Promise { + await this.getTab(tabName).click(); + await this.page.waitForSelector(`[role="tabpanel"]:visible`, { + timeout: 5000, + }); } - getCheckboxByIndex( index: number ) { - return this.page.locator( 'input[type="checkbox"]' ).nth( index ); + getCheckboxByIndex(index: number) { + return this.page.locator('input[type="checkbox"]').nth(index); } - getCheckboxBySetting( settingName: string ) { + getCheckboxBySetting(settingName: string) { const settingMap: { [key: string]: number } = { 'Disable description meta tag': 0, 'Disable skip links': 1, @@ -51,78 +56,106 @@ export default class SettingsPage extends WpAdminPage { 'Deregister Hello reset.css': 0, 'Deregister Hello theme.css': 1, }; - return this.page.locator( `input[type="checkbox"]` ).nth( settingMap[ settingName ] || 0 ); + return this.page + .locator(`input[type="checkbox"]`) + .nth(settingMap[settingName] || 0); } - async toggleSetting( settingName: string ): Promise { - const checkbox = this.getCheckboxBySetting( settingName ); + async toggleSetting(settingName: string): Promise { + const checkbox = this.getCheckboxBySetting(settingName); await checkbox.click(); await this.waitForSaveNotification(); return await checkbox.isChecked(); } - async toggleCheckboxByIndex( index: number ): Promise { - const checkbox = this.getCheckboxByIndex( index ); + async toggleCheckboxByIndex(index: number): Promise { + const checkbox = this.getCheckboxByIndex(index); await checkbox.click(); await this.waitForSaveNotification(); return await checkbox.isChecked(); } async waitForSaveNotification(): Promise { - await expect( this.page.locator( '[role="alert"]:has-text("Settings Saved")' ).first() ).toBeVisible( { timeout: 5000 } ); + await expect( + this.page.locator('[role="alert"]:has-text("Settings Saved")').first(), + ).toBeVisible({ timeout: 5000 }); } async closeSaveNotification(): Promise { - const notification = this.page.locator( '[role="alert"]:has-text("Settings Saved")' ); - if ( await notification.isVisible() ) { - await notification.getByRole( 'button', { name: 'Close' } ).click(); + const notification = this.page.locator( + '[role="alert"]:has-text("Settings Saved")', + ); + if (await notification.isVisible()) { + await notification.getByRole('button', { name: 'Close' }).click(); } } async openChangelog(): Promise { - await this.page.getByRole( 'link', { name: 'Changelog' } ).click(); - await expect( this.page.locator( 'h4:has-text("Changelog")' ) ).toBeVisible( { timeout: 5000 } ); + await this.page.getByRole('link', { name: 'Changelog' }).click(); + await expect(this.page.locator('h4:has-text("Changelog")')).toBeVisible({ + timeout: 5000, + }); } async closeChangelogWithEscape(): Promise { - await this.page.keyboard.press( 'Escape' ); - await expect( this.page.locator( '[role="dialog"]' ) ).not.toBeVisible( { timeout: 3000 } ); + await this.page.keyboard.press('Escape'); + await expect(this.page.locator('[role="dialog"]')).not.toBeVisible({ + timeout: 3000, + }); } async closeChangelogByClickingOutside(): Promise { - await this.page.locator( '[role="dialog"]' ).click( { position: { x: 5, y: 5 } } ); - await expect( this.page.locator( '[role="dialog"]' ) ).not.toBeVisible( { timeout: 3000 } ); + await this.page + .locator('[role="dialog"]') + .click({ position: { x: 5, y: 5 } }); + await expect(this.page.locator('[role="dialog"]')).not.toBeVisible({ + timeout: 3000, + }); } async getChangelogVersions(): Promise { - const versionElements = this.page.locator( 'h6[class*="MuiTypography"]:regex("\\d+\\.\\d+\\.\\d+ - \\d{4}-\\d{2}-\\d{2}")' ); + const versionElements = this.page.locator( + 'h6[class*="MuiTypography"]:regex("\\d+\\.\\d+\\.\\d+ - \\d{4}-\\d{2}-\\d{2}")', + ); return await versionElements.allTextContents(); } async hasWarningAlert(): Promise { - return await this.page.locator( '[role="alert"]:has-text("Be Careful")' ).isVisible(); + return await this.page + .locator('[role="alert"]:has-text("Be Careful")') + .isVisible(); } - async getSettingDescription( settingName: string ): Promise { - const descriptionElement = this.page.locator( `h6:has-text("${ settingName }") ~ * p:has-text("What it does:")` ); - return await descriptionElement.textContent() || ''; + async getSettingDescription(settingName: string): Promise { + const descriptionElement = this.page.locator( + `h6:has-text("${settingName}") ~ * p:has-text("What it does:")`, + ); + return (await descriptionElement.textContent()) || ''; } - async getSettingTip( settingName: string ): Promise { - const tipElement = this.page.locator( `h6:has-text("${ settingName }") ~ * p:has-text("Tip:")` ); - return await tipElement.textContent() || ''; + async getSettingTip(settingName: string): Promise { + const tipElement = this.page.locator( + `h6:has-text("${settingName}") ~ * p:has-text("Tip:")`, + ); + return (await tipElement.textContent()) || ''; } - async getSettingCode( settingName: string ): Promise { - const codeElement = this.page.locator( `h6:has-text("${ settingName }") ~ * code` ); - return await codeElement.textContent() || ''; + async getSettingCode(settingName: string): Promise { + const codeElement = this.page.locator( + `h6:has-text("${settingName}") ~ * code`, + ); + return (await codeElement.textContent()) || ''; } async waitForPageLoad(): Promise { - await this.page.waitForSelector( 'h4:has-text("Advanced theme settings")', { timeout: 10000 } ); - await this.page.waitForSelector( '[role="tablist"]', { timeout: 5000 } ); - await this.page.waitForSelector( '[role="tabpanel"]:visible', { timeout: 5000 } ); - await this.page.waitForTimeout( 1000 ); + await this.page.waitForSelector('h4:has-text("Advanced theme settings")', { + timeout: 10000, + }); + await this.page.waitForSelector('[role="tablist"]', { timeout: 5000 }); + await this.page.waitForSelector('[role="tabpanel"]:visible', { + timeout: 5000, + }); + await this.page.waitForTimeout(1000); } async resetToDefaults(): Promise { @@ -132,22 +165,22 @@ export default class SettingsPage extends WpAdminPage { 'CSS and styling control', ]; - for ( const tabName of tabs ) { - await this.clickTab( tabName ); + for (const tabName of tabs) { + await this.clickTab(tabName); - const checkboxes = this.page.locator( 'input[type="checkbox"]' ); + const checkboxes = this.page.locator('input[type="checkbox"]'); const count = await checkboxes.count(); - for ( let i = 0; i < count; i++ ) { - const checkbox = checkboxes.nth( i ); + for (let i = 0; i < count; i++) { + const checkbox = checkboxes.nth(i); const isChecked = await checkbox.isChecked(); let shouldBeChecked = false; - if ( 'Structure and layout' === tabName && 1 === i ) { + if ('Structure and layout' === tabName && 1 === i) { shouldBeChecked = true; } - if ( isChecked !== shouldBeChecked ) { + if (isChecked !== shouldBeChecked) { await checkbox.click(); await this.waitForSaveNotification(); } diff --git a/tests/playwright/pages/wp-admin-page.ts b/tests/playwright/pages/wp-admin-page.ts index 46f7b12c..2ad9766b 100644 --- a/tests/playwright/pages/wp-admin-page.ts +++ b/tests/playwright/pages/wp-admin-page.ts @@ -1,4 +1,9 @@ -import { type APIRequestContext, type Page, Response, type TestInfo } from '@playwright/test'; +import { + type APIRequestContext, + type Page, + Response, + type TestInfo, +} from '@playwright/test'; import BasePage from './base-page.ts'; import EditorPage from './editor-page.ts'; import { ElementorType, WindowType } from '../types/types.ts'; @@ -9,8 +14,8 @@ let elementor: ElementorType; export default class WpAdminPage extends BasePage { protected readonly apiRequests: ApiRequests; - constructor( page: Page, testInfo: TestInfo, apiRequests: ApiRequests ) { - super( page, testInfo ); + constructor(page: Page, testInfo: TestInfo, apiRequests: ApiRequests) { + super(page, testInfo); this.apiRequests = apiRequests; } @@ -20,7 +25,7 @@ export default class WpAdminPage extends BasePage { * @return {Promise} */ async gotoDashboard(): Promise { - await this.page.goto( '/wp-admin' ); + await this.page.goto('/wp-admin'); } /** @@ -31,17 +36,17 @@ export default class WpAdminPage extends BasePage { async login(): Promise { await this.gotoDashboard(); - const loggedIn = await this.page.$( 'text=Dashboard' ); + const loggedIn = await this.page.$('text=Dashboard'); - if ( loggedIn ) { + if (loggedIn) { return; } - await this.page.waitForSelector( 'text=Log In' ); - await this.page.fill( 'input[name="log"]', process.env.USERNAME ); - await this.page.fill( 'input[name="pwd"]', process.env.PASSWORD ); - await this.page.click( 'text=Log In' ); - await this.page.waitForSelector( 'text=Dashboard' ); + await this.page.waitForSelector('text=Log In'); + await this.page.fill('input[name="log"]', process.env.USERNAME); + await this.page.fill('input[name="pwd"]', process.env.PASSWORD); + await this.page.click('text=Log In'); + await this.page.waitForSelector('text=Dashboard'); } /** @@ -52,19 +57,19 @@ export default class WpAdminPage extends BasePage { * * @return {Promise} */ - async customLogin( username: string, password: string ): Promise { + async customLogin(username: string, password: string): Promise { await this.gotoDashboard(); - const loggedIn = await this.page.$( 'text=Dashboard' ); + const loggedIn = await this.page.$('text=Dashboard'); - if ( loggedIn ) { - await this.page.hover( '#wp-admin-bar-top-secondary' ); - await this.page.click( '#wp-admin-bar-logout > a' ); + if (loggedIn) { + await this.page.hover('#wp-admin-bar-top-secondary'); + await this.page.click('#wp-admin-bar-logout > a'); } - await this.page.fill( 'input[name="log"]', username ); - await this.page.fill( 'input[name="pwd"]', password ); - await this.page.locator( 'text=Log In' ).last().click(); - await this.page.waitForSelector( 'text=Dashboard' ); + await this.page.fill('input[name="log"]', username); + await this.page.fill('input[name="pwd"]', password); + await this.page.locator('text=Log In').last().click(); + await this.page.waitForSelector('text=Dashboard'); } /** @@ -75,18 +80,21 @@ export default class WpAdminPage extends BasePage { * * @return {Promise} A promise that resolves to the new editor page instance. */ - async openNewPage( setWithApi: boolean = true, setPageName: boolean = true ): Promise { - if ( setWithApi ) { + async openNewPage( + setWithApi: boolean = true, + setPageName: boolean = true, + ): Promise { + if (setWithApi) { await this.createNewPostWithAPI(); } else { - await this.createNewPostFromDashboard( setPageName ); + await this.createNewPostFromDashboard(setPageName); } - await this.page.waitForLoadState( 'load', { timeout: 20000 } ); + await this.page.waitForLoadState('load', { timeout: 20000 }); await this.waitForPanel(); await this.closeAnnouncementsIfVisible(); - return new EditorPage( this.page, this.testInfo ); + return new EditorPage(this.page, this.testInfo); } /** @@ -100,13 +108,13 @@ export default class WpAdminPage extends BasePage { title: 'Playwright Test Page - Uninitialized', content: '', }, - postId = await this.apiRequests.create( request, 'pages', postDataInitial ), + postId = await this.apiRequests.create(request, 'pages', postDataInitial), postDataUpdated = { - title: `Playwright Test Page #${ postId }`, + title: `Playwright Test Page #${postId}`, }; - await this.apiRequests.create( request, `pages/${ postId }`, postDataUpdated ); - await this.page.goto( `/wp-admin/post.php?post=${ postId }&action=elementor` ); + await this.apiRequests.create(request, `pages/${postId}`, postDataUpdated); + await this.page.goto(`/wp-admin/post.php?post=${postId}&action=elementor`); return postId; } @@ -118,14 +126,14 @@ export default class WpAdminPage extends BasePage { * * @return {Promise} */ - async createNewPostFromDashboard( setPageName: boolean ): Promise { - if ( ! await this.page.$( '.e-overview__create > a' ) ) { + async createNewPostFromDashboard(setPageName: boolean): Promise { + if (!(await this.page.$('.e-overview__create > a'))) { await this.gotoDashboard(); } - await this.page.click( '.e-overview__create > a' ); + await this.page.click('.e-overview__create > a'); - if ( ! setPageName ) { + if (!setPageName) { return; } @@ -138,14 +146,20 @@ export default class WpAdminPage extends BasePage { * @return {Promise} */ async setPageName(): Promise { - await this.page.locator( '#elementor-panel-footer-settings' ).click(); - - const pageId = await this.page.evaluate( () => elementor.config.initialDocument.id ); - await this.page.locator( '.elementor-control-post_title input' ).fill( `Playwright Test Page #${ pageId }` ); - - await this.page.locator( '#elementor-panel-footer-saver-options' ).click(); - await this.page.locator( '#elementor-panel-footer-sub-menu-item-save-draft' ).click(); - await this.page.locator( '#elementor-panel-header-add-button' ).click(); + await this.page.locator('#elementor-panel-footer-settings').click(); + + const pageId = await this.page.evaluate( + () => elementor.config.initialDocument.id, + ); + await this.page + .locator('.elementor-control-post_title input') + .fill(`Playwright Test Page #${pageId}`); + + await this.page.locator('#elementor-panel-footer-saver-options').click(); + await this.page + .locator('#elementor-panel-footer-sub-menu-item-save-draft') + .click(); + await this.page.locator('#elementor-panel-header-add-button').click(); } /** @@ -154,18 +168,20 @@ export default class WpAdminPage extends BasePage { * @return {Promise} A promise that resolves to the editor page instance. */ async convertFromGutenberg(): Promise { - await Promise.all( [ - this.page.waitForResponse( async ( response ) => await this.blockUrlResponse( response ) ), - this.page.click( '#elementor-switch-mode' ), - ] ); - - await this.page.waitForURL( '**/post.php?post=*&action=elementor' ); - await this.page.waitForLoadState( 'load', { timeout: 20000 } ); + await Promise.all([ + this.page.waitForResponse( + async (response) => await this.blockUrlResponse(response), + ), + this.page.click('#elementor-switch-mode'), + ]); + + await this.page.waitForURL('**/post.php?post=*&action=elementor'); + await this.page.waitForLoadState('load', { timeout: 20000 }); await this.waitForPanel(); await this.closeAnnouncementsIfVisible(); - return new EditorPage( this.page, this.testInfo ); + return new EditorPage(this.page, this.testInfo); } /** @@ -175,10 +191,12 @@ export default class WpAdminPage extends BasePage { * * @return {Promise} A promise that resolves to true if the response is a valid REST/JSON request with a 200 status. */ - async blockUrlResponse( response: Response ): Promise { - const isRestRequest = response.url().includes( 'rest_route=%2Fwp%2Fv2%2Fpages%2' ); // For local testing - const isJsonRequest = response.url().includes( 'wp-json/wp/v2/pages' ); // For CI testing - return ( isJsonRequest || isRestRequest ) && 200 === response.status(); + async blockUrlResponse(response: Response): Promise { + const isRestRequest = response + .url() + .includes('rest_route=%2Fwp%2Fv2%2Fpages%2'); // For local testing + const isJsonRequest = response.url().includes('wp-json/wp/v2/pages'); // For CI testing + return (isJsonRequest || isRestRequest) && 200 === response.status(); } /** @@ -187,8 +205,10 @@ export default class WpAdminPage extends BasePage { * @return {Promise} */ async waitForPanel(): Promise { - await this.page.waitForSelector( '.elementor-panel-loading', { state: 'detached' } ); - await this.page.waitForSelector( '#elementor-loading', { state: 'hidden' } ); + await this.page.waitForSelector('.elementor-panel-loading', { + state: 'detached', + }); + await this.page.waitForSelector('#elementor-loading', { state: 'hidden' }); } /** @@ -201,36 +221,43 @@ export default class WpAdminPage extends BasePage { * * @return {Promise} */ - async setExperiments( experiments: { [ n: string ]: boolean | string }, oldUrl: boolean = false ): Promise { - if ( oldUrl ) { - await this.page.goto( '/wp-admin/admin.php?page=elementor#tab-experiments' ); - await this.page.click( '#elementor-settings-tab-experiments' ); + async setExperiments( + experiments: { [n: string]: boolean | string }, + oldUrl: boolean = false, + ): Promise { + if (oldUrl) { + await this.page.goto( + '/wp-admin/admin.php?page=elementor#tab-experiments', + ); + await this.page.click('#elementor-settings-tab-experiments'); } else { - await this.page.goto( '/wp-admin/admin.php?page=elementor-settings#tab-experiments' ); + await this.page.goto( + '/wp-admin/admin.php?page=elementor-settings#tab-experiments', + ); } const prefix = 'e-experiment'; - for ( const [ id, state ] of Object.entries( experiments ) ) { - const selector = `#${ prefix }-${ id }`; + for (const [id, state] of Object.entries(experiments)) { + const selector = `#${prefix}-${id}`; // Try to make the element visible - Since some experiments may be hidden for the user, // but actually exist and need to be tested. - await this.page.evaluate( ( el ) => { - const element: HTMLElement = document.querySelector( el ); + await this.page.evaluate((el) => { + const element: HTMLElement = document.querySelector(el); - if ( element ) { + if (element) { element.style.display = 'block'; } - }, `.elementor_experiment-${ id }` ); + }, `.elementor_experiment-${id}`); - await this.page.selectOption( selector, state ? 'active' : 'inactive' ); + await this.page.selectOption(selector, state ? 'active' : 'inactive'); // Click to confirm any experiment that has dependencies. await this.confirmExperimentModalIfOpen(); } - await this.page.click( '#submit' ); + await this.page.click('#submit'); } /** @@ -239,8 +266,10 @@ export default class WpAdminPage extends BasePage { * @return {Promise} */ async resetExperiments(): Promise { - await this.page.goto( '/wp-admin/admin.php?page=elementor-settings#tab-experiments' ); - await this.page.getByRole( 'button', { name: 'default' } ).click(); + await this.page.goto( + '/wp-admin/admin.php?page=elementor-settings#tab-experiments', + ); + await this.page.getByRole('button', { name: 'default' }).click(); } /** @@ -251,26 +280,31 @@ export default class WpAdminPage extends BasePage { * * @return {Promise} */ - async setSiteLanguage( language: string, userLanguage: string = null ): Promise { + async setSiteLanguage( + language: string, + userLanguage: string = null, + ): Promise { let languageCheck = language; - if ( 'he_IL' === language ) { + if ('he_IL' === language) { languageCheck = 'he-IL'; - } else if ( '' === language ) { + } else if ('' === language) { languageCheck = 'en_US'; } - await this.page.goto( '/wp-admin/options-general.php' ); + await this.page.goto('/wp-admin/options-general.php'); - const isLanguageActive = await this.page.locator( 'html[lang=' + languageCheck + ']' ).isVisible(); + const isLanguageActive = await this.page + .locator('html[lang=' + languageCheck + ']') + .isVisible(); - if ( ! isLanguageActive ) { - await this.page.selectOption( '#WPLANG', language ); - await this.page.locator( '#submit' ).click(); + if (!isLanguageActive) { + await this.page.selectOption('#WPLANG', language); + await this.page.locator('#submit').click(); } const userProfileLanguage = null !== userLanguage ? userLanguage : language; - await this.setUserLanguage( userProfileLanguage ); + await this.setUserLanguage(userProfileLanguage); } /** @@ -280,10 +314,10 @@ export default class WpAdminPage extends BasePage { * * @return {Promise} */ - async setUserLanguage( language: string ): Promise { - await this.page.goto( 'wp-admin/profile.php' ); - await this.page.selectOption( '[name="locale"]', language ); - await this.page.locator( '#submit' ).click(); + async setUserLanguage(language: string): Promise { + await this.page.goto('wp-admin/profile.php'); + await this.page.selectOption('[name="locale"]', language); + await this.page.locator('#submit').click(); } /** @@ -292,14 +326,16 @@ export default class WpAdminPage extends BasePage { * @return {Promise} */ async confirmExperimentModalIfOpen(): Promise { - const dialogButton = this.page.locator( '.dialog-type-confirm .dialog-confirm-ok' ); + const dialogButton = this.page.locator( + '.dialog-type-confirm .dialog-confirm-ok', + ); - if ( await dialogButton.isVisible() ) { + if (await dialogButton.isVisible()) { await dialogButton.click(); // Clicking the confirm button - "Activate" or "Deactivate" - will immediately save the existing experiments, // so we need to wait for the page to save and reload before we continue on to set any more experiments. - await this.page.waitForLoadState( 'load' ); + await this.page.waitForLoadState('load'); } } @@ -310,12 +346,12 @@ export default class WpAdminPage extends BasePage { */ async getActiveTheme(): Promise { const request: APIRequestContext = this.page.context().request; - const themeData = await this.apiRequests.getTheme( request, 'active' ); - return themeData[ 0 ].stylesheet; + const themeData = await this.apiRequests.getTheme(request, 'active'); + return themeData[0].stylesheet; } - async activateTheme( theme: string ) { - await wpCli( `wp theme activate ${ theme }` ); + async activateTheme(theme: string) { + await wpCli(`wp theme activate ${theme}`); } /** @@ -324,9 +360,13 @@ export default class WpAdminPage extends BasePage { * @return {Promise} */ async enableAdvancedUploads(): Promise { - await this.page.goto( '/wp-admin/admin.php?page=elementor-settings#tab-advanced' ); - await this.page.locator( 'select[name="elementor_unfiltered_files_upload"]' ).selectOption( '1' ); - await this.page.getByRole( 'button', { name: 'Save Changes' } ).click(); + await this.page.goto( + '/wp-admin/admin.php?page=elementor-settings#tab-advanced', + ); + await this.page + .locator('select[name="elementor_unfiltered_files_upload"]') + .selectOption('1'); + await this.page.getByRole('button', { name: 'Save Changes' }).click(); } /** @@ -335,9 +375,13 @@ export default class WpAdminPage extends BasePage { * @return {Promise} */ async disableAdvancedUploads(): Promise { - await this.page.goto( '/wp-admin/admin.php?page=elementor-settings#tab-advanced' ); - await this.page.locator( 'select[name="elementor_unfiltered_files_upload"]' ).selectOption( '' ); - await this.page.getByRole( 'button', { name: 'Save Changes' } ).click(); + await this.page.goto( + '/wp-admin/admin.php?page=elementor-settings#tab-advanced', + ); + await this.page + .locator('select[name="elementor_unfiltered_files_upload"]') + .selectOption(''); + await this.page.getByRole('button', { name: 'Save Changes' }).click(); } /** @@ -346,15 +390,21 @@ export default class WpAdminPage extends BasePage { * @return {Promise} */ async closeAnnouncementsIfVisible(): Promise { - if ( await this.page.locator( '#e-announcements-root' ).count() > 0 ) { - await this.page.evaluate( ( selector ) => document.getElementById( selector ).remove(), 'e-announcements-root' ); + if ((await this.page.locator('#e-announcements-root').count()) > 0) { + await this.page.evaluate( + (selector) => document.getElementById(selector).remove(), + 'e-announcements-root', + ); } let window: WindowType; - await this.page.evaluate( () => { + await this.page.evaluate(() => { // @ts-ignore editor session is on the window object const editorSessionId = window.EDITOR_SESSION_ID; - window.sessionStorage.setItem( 'ai_promotion_introduction_editor_session_key', editorSessionId ); - } ); + window.sessionStorage.setItem( + 'ai_promotion_introduction_editor_session_key', + editorSessionId, + ); + }); } /** @@ -363,7 +413,9 @@ export default class WpAdminPage extends BasePage { * @return {Promise} */ async editWithElementor(): Promise { - await this.page.getByRole( 'link', { name: ' Edit with Elementor' } ).click(); + await this.page + .getByRole('link', { name: ' Edit with Elementor' }) + .click(); } /** @@ -372,9 +424,9 @@ export default class WpAdminPage extends BasePage { * @return {Promise} */ async closeBlockEditorPopupIfVisible(): Promise { - await this.page.locator( '#elementor-switch-mode-button' ).waitFor(); - if ( await this.page.getByRole( 'button', { name: 'Close' } ).isVisible() ) { - await this.page.getByRole( 'button', { name: 'Close' } ).click(); + await this.page.locator('#elementor-switch-mode-button').waitFor(); + if (await this.page.getByRole('button', { name: 'Close' }).isVisible()) { + await this.page.getByRole('button', { name: 'Close' }).click(); } } @@ -384,7 +436,7 @@ export default class WpAdminPage extends BasePage { * @return {Promise} */ async openNewWordpressPage(): Promise { - await this.page.goto( '/wp-admin/post-new.php?post_type=page' ); + await this.page.goto('/wp-admin/post-new.php?post_type=page'); await this.closeBlockEditorPopupIfVisible(); } @@ -394,9 +446,9 @@ export default class WpAdminPage extends BasePage { * @return {Promise} */ async hideAdminBar(): Promise { - await this.page.goto( '/wp-admin/profile.php' ); - await this.page.locator( '#admin_bar_front' ).uncheck(); - await this.page.locator( '#submit' ).click(); + await this.page.goto('/wp-admin/profile.php'); + await this.page.locator('#admin_bar_front').uncheck(); + await this.page.locator('#submit').click(); } /** @@ -405,8 +457,8 @@ export default class WpAdminPage extends BasePage { * @return {Promise} */ async showAdminBar(): Promise { - await this.page.goto( '/wp-admin/profile.php' ); - await this.page.locator( '#admin_bar_front' ).check(); - await this.page.locator( '#submit' ).click(); + await this.page.goto('/wp-admin/profile.php'); + await this.page.locator('#admin_bar_front').check(); + await this.page.locator('#submit').click(); } } diff --git a/tests/playwright/parallelTest.ts b/tests/playwright/parallelTest.ts index 90e84483..c5be4dc2 100644 --- a/tests/playwright/parallelTest.ts +++ b/tests/playwright/parallelTest.ts @@ -4,49 +4,79 @@ import path from 'path'; import { fetchNonce, login } from './wp-authentication.ts'; import ApiRequests from './assets/api-requests.ts'; -export const parallelTest = baseTest.extend< NonNullable, { workerStorageState: string, workerBaseURL: string, apiRequests: ApiRequests }>( { +export const parallelTest = baseTest.extend< + NonNullable, + { + workerStorageState: string; + workerBaseURL: string; + apiRequests: ApiRequests; + } +>({ // Use the same storage state for all tests in this worker. - baseURL: ( { workerBaseURL }, use ) => use( workerBaseURL ), - workerBaseURL: [ async ( {}, use ) => { - await use( process.env.BASE_URL || ( ( 1 === Number( process.env.TEST_PARALLEL_INDEX ) ) - ? process.env.TEST_SERVER - : process.env.DEV_SERVER ), - ); - }, { scope: 'worker' } ], + baseURL: ({ workerBaseURL }, use) => use(workerBaseURL), + workerBaseURL: [ + async ({}, use) => { + await use( + process.env.BASE_URL || + (1 === Number(process.env.TEST_PARALLEL_INDEX) + ? process.env.TEST_SERVER + : process.env.DEV_SERVER), + ); + }, + { scope: 'worker' }, + ], // Use the same storage state for all tests in this worker. - storageState: ( { workerStorageState }, use ) => use( workerStorageState ), + storageState: ({ workerStorageState }, use) => use(workerStorageState), // Authenticate once per worker with a worker-scoped fixture. - workerStorageState: [ async ( { workerBaseURL }, use, testInfo ) => { - // Use parallelIndex as a unique identifier for each worker. - const id = testInfo.workerIndex; - const fileName = path.resolve( testInfo.project.outputDir, `.storageState-${ id }.json` ); + workerStorageState: [ + async ({ workerBaseURL }, use, testInfo) => { + // Use parallelIndex as a unique identifier for each worker. + const id = testInfo.workerIndex; + const fileName = path.resolve( + testInfo.project.outputDir, + `.storageState-${id}.json`, + ); - if ( fs.existsSync( fileName ) ) { - // Reuse existing authentication state if any. - await use( fileName ); - return; - } + if (fs.existsSync(fileName)) { + // Reuse existing authentication state if any. + await use(fileName); + return; + } - // Send authentication request. - const context = await login( request, process.env.USERNAME || 'admin', process.env.PASSWORD || 'password', workerBaseURL ); - await context.storageState( { path: fileName } ); - await context.dispose(); + // Send authentication request. + const context = await login( + request, + process.env.USERNAME || 'admin', + process.env.PASSWORD || 'password', + workerBaseURL, + ); + await context.storageState({ path: fileName }); + await context.dispose(); - await use( fileName ); - }, { scope: 'worker' } ], + await use(fileName); + }, + { scope: 'worker' }, + ], // Use the same storage state for all tests in this worker. - apiRequests: [ async ( { workerStorageState, workerBaseURL }, use ) => { - const context = await request.newContext( { storageState: workerStorageState } ); - try { - const nonce = await fetchNonce( context, workerBaseURL ); - const apiRequests = new ApiRequests( workerBaseURL, nonce ); - await use( apiRequests ); - } catch ( e ) { - throw new Error( `Failed to fetch Nonce. Base URL: ${ workerBaseURL }, Storage State: ${ workerStorageState } `, { cause: e } ); - } - }, { scope: 'worker' } ], - -} ); + apiRequests: [ + async ({ workerStorageState, workerBaseURL }, use) => { + const context = await request.newContext({ + storageState: workerStorageState, + }); + try { + const nonce = await fetchNonce(context, workerBaseURL); + const apiRequests = new ApiRequests(workerBaseURL, nonce); + await use(apiRequests); + } catch (e) { + throw new Error( + `Failed to fetch Nonce. Base URL: ${workerBaseURL}, Storage State: ${workerStorageState} `, + { cause: e }, + ); + } + }, + { scope: 'worker' }, + ], +}); diff --git a/tests/playwright/playwright.config.ts b/tests/playwright/playwright.config.ts index 766597f5..79a3b83a 100644 --- a/tests/playwright/playwright.config.ts +++ b/tests/playwright/playwright.config.ts @@ -5,13 +5,14 @@ import { timeouts } from './config/timeouts.ts'; process.env.DEV_SERVER = 'http://localhost:8888'; process.env.TEST_SERVER = 'http://localhost:8889'; -process.env.DEBUG_PORT = ( 1 === Number( process.env.TEST_PARALLEL_INDEX ) ) ? '9223' : '9222'; +process.env.DEBUG_PORT = + 1 === Number(process.env.TEST_PARALLEL_INDEX) ? '9223' : '9222'; -_config( { - path: resolve( __dirname, '../../.env' ), -} ); +_config({ + path: resolve(__dirname, '../../.env'), +}); -export default defineConfig( { +export default defineConfig({ testDir: './tests', timeout: timeouts.singleTest, globalTimeout: timeouts.global, @@ -20,28 +21,27 @@ export default defineConfig( { toMatchSnapshot: { maxDiffPixelRatio: 0.03 }, toHaveScreenshot: { maxDiffPixelRatio: 0.03 }, }, - forbidOnly: !! process.env.CI, + forbidOnly: !!process.env.CI, retries: process.env.CI ? 9 : 0, workers: process.env.CI ? 2 : 1, fullyParallel: false, - reporter: process.env.CI - ? [ [ 'github' ], [ 'list' ] ] - : [ [ 'list' ] ], + reporter: process.env.CI ? [['github'], ['list']] : [['list']], use: { launchOptions: { - args: [ `--remote-debugging-port=${ process.env.DEBUG_PORT }` ], + args: [`--remote-debugging-port=${process.env.DEBUG_PORT}`], }, - headless: !! process.env.CI, + headless: !!process.env.CI, ignoreHTTPSErrors: true, actionTimeout: 30000, // Increase from the default navigationTimeout: 45000, // Increase from the default trace: 'retain-on-failure', video: process.env.CI ? 'retain-on-failure' : 'off', - baseURL: process.env.BASE_URL || - ( ( 1 === Number( process.env.TEST_PARALLEL_INDEX ) ) + baseURL: + process.env.BASE_URL || + (1 === Number(process.env.TEST_PARALLEL_INDEX) ? process.env.TEST_SERVER - : process.env.DEV_SERVER ), + : process.env.DEV_SERVER), viewport: { width: 1920, height: 1080 }, - storageState: `./storageState-${ process.env.TEST_PARALLEL_INDEX }.json`, + storageState: `./storageState-${process.env.TEST_PARALLEL_INDEX}.json`, }, -} ); +}); diff --git a/tests/playwright/selectors/editor-selectors.ts b/tests/playwright/selectors/editor-selectors.ts index b1a45a35..10dfbc8e 100644 --- a/tests/playwright/selectors/editor-selectors.ts +++ b/tests/playwright/selectors/editor-selectors.ts @@ -1,5 +1,5 @@ const EditorSelectors = { - getWidgetByName: ( title: string ) => `[data-widget_type="${ title }.default"]`, + getWidgetByName: (title: string) => `[data-widget_type="${title}.default"]`, widget: '[data-element_type="widget"]', container: '[data-element_type="container"]', item: '.elementor-repeater-row-item-title', @@ -30,7 +30,8 @@ const EditorSelectors = { saveButton: '//button[text()="Save Changes"]', layout: { breakpoints: { - removeBreakpointButton: '#elementor-kit-panel-content .select2-selection__choice__remove', + removeBreakpointButton: + '#elementor-kit-panel-content .select2-selection__choice__remove', }, }, }, @@ -51,12 +52,13 @@ const EditorSelectors = { popoverCard: '[data-testid="e-popover-card"]', }, refreshPopup: { - reloadButton: '#elementor-save-kit-refresh-page .dialog-button.dialog-ok.dialog-alert-ok', + reloadButton: + '#elementor-save-kit-refresh-page .dialog-button.dialog-ok.dialog-alert-ok', }, media: { preview: '.elementor-control-media__preview', - imageByTitle: ( imageTitle: string ) => `[aria-label="${ imageTitle }"]`, + imageByTitle: (imageTitle: string) => `[aria-label="${imageTitle}"]`, selectBtn: '.button.media-button', imageInp: 'input[type="file"]', addGalleryButton: 'button.media-button-gallery', @@ -65,7 +67,7 @@ const EditorSelectors = { imgDescription: '#attachment-details-description', }, button: { - getByName: ( name: string ) => `.elementor-button:has-text("${ name }")`, + getByName: (name: string) => `.elementor-button:has-text("${name}")`, id: '[data-setting="button_css_id"]', url: 'input[data-setting="url"]', linkOptions: 'button[data-tooltip="Link Options"]', @@ -76,7 +78,7 @@ const EditorSelectors = { heading: { h2: 'h2.elementor-heading-title', get link() { - return `${ this.h2 } a`; + return `${this.h2} a`; }, }, image: { @@ -86,27 +88,27 @@ const EditorSelectors = { widthInp: 'input[data-setting="width"]', heightInp: 'input[data-setting="height"]', get image() { - return `${ this.widget } img`; + return `${this.widget} img`; }, get link() { - return `${ this.widget } a`; + return `${this.widget} a`; }, lightBox: '.swiper-zoom-container', }, icon: { widget: '[data-widget_type="icon.default"]', get link() { - return `${ this.widget } a`; + return `${this.widget} a`; }, }, imageBox: { widget: '[data-widget_type="image-box.default"]', imageSizeSelect: 'thumbnail_size', get link() { - return `${ this.widget } a`; + return `${this.widget} a`; }, get image() { - return `${ this.widget } img`; + return `${this.widget} img`; }, }, galleryControl: { @@ -115,7 +117,7 @@ const EditorSelectors = { imageCarousel: { widget: '[data-widget_type="image-carousel.default"]', get link() { - return `${ this.widget } a`; + return `${this.widget} a`; }, navigationSelect: '.elementor-control-navigation select', autoplaySelect: 'input[data-setting="autoplay"]', @@ -126,22 +128,23 @@ const EditorSelectors = { imgCaption: 'figcaption.elementor-image-carousel-caption', prevSliderBtn: '.elementor-swiper-button-prev', nextSliderBtn: '.elementor-swiper-button-next', - activeSlide: ( id: string ) => `.swiper-pagination-bullet-active[aria-label="Go to slide ${ id }"]`, - activeSlideImg: ( name: string ) => `.swiper-slide-active img[alt="${ name }"]`, + activeSlide: (id: string) => + `.swiper-pagination-bullet-active[aria-label="Go to slide ${id}"]`, + activeSlideImg: (name: string) => `.swiper-slide-active img[alt="${name}"]`, }, textPath: { widget: '[data-widget_type="text-path.default"]', get link() { - return `${ this.widget } a`; + return `${this.widget} a`; }, get svgIcon() { - return `${ this.widget } svg path.st0`; + return `${this.widget} svg path.st0`; }, }, video: { widget: '[data-widget_type="video.default"]', get image() { - return `${ this.widget } .elementor-custom-embed-image-overlay`; + return `${this.widget} .elementor-custom-embed-image-overlay`; }, lightBoxControlInp: '[data-setting="lightbox"]', lightBoxSetting: 'div[data-elementor-open-lightbox="yes"]', @@ -153,10 +156,10 @@ const EditorSelectors = { socialIcons: { widget: '[data-widget_type="social-icons.default"]', get link() { - return `${ this.widget } a`; + return `${this.widget} a`; }, get svgIcon() { - return `${ this.widget } svg path.st0`; + return `${this.widget} svg path.st0`; }, }, tabs: { @@ -216,8 +219,10 @@ const EditorSelectors = { }, contextMenu: { menu: '.elementor-context-menu', - saveAsGlobal: '.elementor-context-menu-list__item.elementor-context-menu-list__item-save.elementor-context-menu-list__item--disabled', - notes: '.elementor-context-menu-list__item.elementor-context-menu-list__item-open_notes.elementor-context-menu-list__item--disabled', + saveAsGlobal: + '.elementor-context-menu-list__item.elementor-context-menu-list__item-save.elementor-context-menu-list__item--disabled', + notes: + '.elementor-context-menu-list__item.elementor-context-menu-list__item-open_notes.elementor-context-menu-list__item--disabled', }, dialog: { lightBox: '.elementor-lightbox', diff --git a/tests/playwright/selectors/top-bar-selectors.ts b/tests/playwright/selectors/top-bar-selectors.ts index 6be6cebc..1da2444a 100644 --- a/tests/playwright/selectors/top-bar-selectors.ts +++ b/tests/playwright/selectors/top-bar-selectors.ts @@ -1,7 +1,7 @@ export type TopBarSelector = { attribute: string; attributeValue: string; -} +}; export default { elementorLogo: { diff --git a/tests/playwright/tests/admin/hello-theme-admin-home.test.ts b/tests/playwright/tests/admin/hello-theme-admin-home.test.ts index c33e71c2..fa268797 100644 --- a/tests/playwright/tests/admin/hello-theme-admin-home.test.ts +++ b/tests/playwright/tests/admin/hello-theme-admin-home.test.ts @@ -1,22 +1,32 @@ import { parallelTest as test } from '../../parallelTest.ts'; import { expect } from '@playwright/test'; -test.describe( 'Hello Theme Admin Home Page', () => { - test.beforeEach( async ( { page } ) => { - await page.goto( '/wp-admin/admin.php?page=hello-elementor' ); - } ); +test.describe('Hello Theme Admin Home Page', () => { + test.beforeEach(async ({ page }) => { + await page.goto('/wp-admin/admin.php?page=hello-elementor'); + }); - test( 'should display Welcome to Hello Theme message and take screenshot', async ( { page } ) => { - const welcomeSection = page.locator( 'text=Go Pro, Go Limitless' ).locator( '..' ).locator( '..' ); - await expect.soft( welcomeSection ).toHaveScreenshot( 'welcome-section.png' ); - } ); + test('should display Welcome to Hello Theme message and take screenshot', async ({ + page, + }) => { + const welcomeSection = page + .locator('text=Go Pro, Go Limitless') + .locator('..') + .locator('..'); + await expect.soft(welcomeSection).toHaveScreenshot('welcome-section.png'); + }); - test( 'should display Quick Links section', async ( { page } ) => { - const quickLinksHeading = page.locator( 'h6:has-text("Quick Links")' ); - await expect( quickLinksHeading ).toBeVisible(); - await expect( quickLinksHeading ).toContainText( 'Quick Links' ); - const quickLinksSection = page.locator( 'text=Quick Links' ).locator( '..' ).locator( '..' ); - await expect( quickLinksSection ).toContainText( 'These quick actions will get your site airborne in a flash.' ); + test('should display Quick Links section', async ({ page }) => { + const quickLinksHeading = page.locator('h6:has-text("Quick Links")'); + await expect(quickLinksHeading).toBeVisible(); + await expect(quickLinksHeading).toContainText('Quick Links'); + const quickLinksSection = page + .locator('text=Quick Links') + .locator('..') + .locator('..'); + await expect(quickLinksSection).toContainText( + 'These quick actions will get your site airborne in a flash.', + ); const expectedQuickLinks = [ 'Site Name', 'Site Logo', @@ -24,57 +34,69 @@ test.describe( 'Hello Theme Admin Home Page', () => { 'Site Colors', 'Site Fonts', ]; - for ( const linkText of expectedQuickLinks ) { - const linkElement = page.locator( `h6:has-text("${ linkText }")` ); - await expect( linkElement ).toBeVisible(); + for (const linkText of expectedQuickLinks) { + const linkElement = page.locator(`h6:has-text("${linkText}")`); + await expect(linkElement).toBeVisible(); } - } ); + }); - test.skip( 'should navigate to correct pages from Quick Links', async ( { page } ) => { + test.skip('should navigate to correct pages from Quick Links', async ({ + page, + }) => { const quickLinksTests = [ { linkText: 'Site Name', - expectedUrlPattern: /post\.php\?post=\d+&action=elementor.*active-tab=settings-site-identity.*active-document=\d+/, - expectedPageSection: '.elementor-control-section_settings-site-identity', + expectedUrlPattern: + /post\.php\?post=\d+&action=elementor.*active-tab=settings-site-identity.*active-document=\d+/, + expectedPageSection: + '.elementor-control-section_settings-site-identity', }, { linkText: 'Site Logo', - expectedUrlPattern: /post\.php\?post=\d+&action=elementor.*active-tab=settings-site-identity.*active-document=\d+/, - expectedPageSection: '.elementor-control-section_settings-site-identity', + expectedUrlPattern: + /post\.php\?post=\d+&action=elementor.*active-tab=settings-site-identity.*active-document=\d+/, + expectedPageSection: + '.elementor-control-section_settings-site-identity', }, { linkText: 'Site Favicon', - expectedUrlPattern: /post\.php\?post=\d+&action=elementor.*active-tab=settings-site-identity.*active-document=\d+/, - expectedPageSection: '.elementor-control-section_settings-site-identity', + expectedUrlPattern: + /post\.php\?post=\d+&action=elementor.*active-tab=settings-site-identity.*active-document=\d+/, + expectedPageSection: + '.elementor-control-section_settings-site-identity', }, { linkText: 'Site Colors', - expectedUrlPattern: /post\.php\?post=\d+&action=elementor.*active-tab=global-colors.*active-document=\d+/, + expectedUrlPattern: + /post\.php\?post=\d+&action=elementor.*active-tab=global-colors.*active-document=\d+/, expectedPageSection: '.elementor-control-section_global_colors', }, { linkText: 'Site Fonts', - expectedUrlPattern: /post\.php\?post=\d+&action=elementor.*active-tab=global-typography.*active-document=\d+/, + expectedUrlPattern: + /post\.php\?post=\d+&action=elementor.*active-tab=global-typography.*active-document=\d+/, expectedPageSection: '.elementor-control-section_text_style', }, ]; - for ( const linkTest of quickLinksTests ) { - await test.step( linkTest.linkText, async () => { - await page.goto( '/wp-admin/admin.php?page=hello-elementor' ); - const linkElement = page.locator( `h6:has-text("${ linkTest.linkText }") a` ); - await expect( linkElement ).toBeVisible(); + for (const linkTest of quickLinksTests) { + await test.step(linkTest.linkText, async () => { + await page.goto('/wp-admin/admin.php?page=hello-elementor'); + const linkElement = page.locator( + `h6:has-text("${linkTest.linkText}") a`, + ); + await expect(linkElement).toBeVisible(); - await Promise.all( [ - page.waitForURL( linkTest.expectedUrlPattern ), + await Promise.all([ + page.waitForURL(linkTest.expectedUrlPattern), linkElement.click(), - ] ); + ]); - expect( page.url() ).toMatch( linkTest.expectedUrlPattern ); - await page.waitForSelector( '#elementor-kit-panel' ); - const location = page.locator( linkTest.expectedPageSection ); - await expect( location ).toBeVisible(); - } ); + expect(page.url()).toMatch(linkTest.expectedUrlPattern); + await page.waitForSelector('#elementor-kit-panel'); + const location = page.locator(linkTest.expectedPageSection); + await expect(location).toBeVisible(); + }); } - } ); -} ); + }); +}); diff --git a/tests/playwright/tests/settings/description-meta-tag.test.ts b/tests/playwright/tests/settings/description-meta-tag.test.ts index 4260612c..60477701 100644 --- a/tests/playwright/tests/settings/description-meta-tag.test.ts +++ b/tests/playwright/tests/settings/description-meta-tag.test.ts @@ -2,59 +2,74 @@ import { parallelTest as test } from '../../parallelTest.ts'; import { expect } from '@playwright/test'; import SettingsPage from '../../pages/settings-page.ts'; -test.describe.serial( 'Description Meta Tag Setting - Behavior Tests', () => { +test.describe.serial('Description Meta Tag Setting - Behavior Tests', () => { let postId: string; - test.beforeEach( async ( { page, apiRequests }, testInfo ) => { - const settingsPage = new SettingsPage( page, testInfo, apiRequests ); + test.beforeEach(async ({ page, apiRequests }, testInfo) => { + const settingsPage = new SettingsPage(page, testInfo, apiRequests); postId = await settingsPage.createNewBasicPost(); await settingsPage.gotoSettingsPage(); await settingsPage.waitForPageLoad(); - await settingsPage.clickTab( 'SEO and accessibility' ); - } ); + await settingsPage.clickTab('SEO and accessibility'); + }); - test( 'should not show meta description on page when the "Disable description meta tag" setting is enabled', async ( { page, apiRequests }, testInfo ) => { - const settingsPage = new SettingsPage( page, testInfo, apiRequests ); - const metaCheckbox = settingsPage.getCheckboxBySetting( 'Disable description meta tag' ); + test('should not show meta description on page when the "Disable description meta tag" setting is enabled', async ({ + page, + apiRequests, + }, testInfo) => { + const settingsPage = new SettingsPage(page, testInfo, apiRequests); + const metaCheckbox = settingsPage.getCheckboxBySetting( + 'Disable description meta tag', + ); const isChecked = await metaCheckbox.isChecked(); - if ( ! isChecked ) { - await settingsPage.toggleSetting( 'Disable description meta tag' ); + if (!isChecked) { + await settingsPage.toggleSetting('Disable description meta tag'); } - await page.goto( `/?p=${ postId }` ); + await page.goto(`/?p=${postId}`); - const metaDescription = page.locator( 'meta[name="description"]' ); - await expect( metaDescription ).not.toBeAttached(); - } ); + const metaDescription = page.locator('meta[name="description"]'); + await expect(metaDescription).not.toBeAttached(); + }); - test( 'should show meta description on page when the "Disable description meta tag" setting is disabled', async ( { page, apiRequests }, testInfo ) => { - const settingsPage = new SettingsPage( page, testInfo, apiRequests ); - const metaCheckbox = settingsPage.getCheckboxBySetting( 'Disable description meta tag' ); + test('should show meta description on page when the "Disable description meta tag" setting is disabled', async ({ + page, + apiRequests, + }, testInfo) => { + const settingsPage = new SettingsPage(page, testInfo, apiRequests); + const metaCheckbox = settingsPage.getCheckboxBySetting( + 'Disable description meta tag', + ); const isChecked = await metaCheckbox.isChecked(); - if ( isChecked ) { - await settingsPage.toggleSetting( 'Disable description meta tag' ); + if (isChecked) { + await settingsPage.toggleSetting('Disable description meta tag'); } - await page.goto( `/?p=${ postId }` ); + await page.goto(`/?p=${postId}`); - const metaDescription = page.locator( 'meta[name="description"]' ); - await expect( metaDescription ).toBeAttached(); - } ); + const metaDescription = page.locator('meta[name="description"]'); + await expect(metaDescription).toBeAttached(); + }); - test( 'should not show meta description on home page when the "Disable description meta tag" setting is enabled', async ( { page, apiRequests }, testInfo ) => { - const settingsPage = new SettingsPage( page, testInfo, apiRequests ); - const metaCheckbox = settingsPage.getCheckboxBySetting( 'Disable description meta tag' ); + test('should not show meta description on home page when the "Disable description meta tag" setting is enabled', async ({ + page, + apiRequests, + }, testInfo) => { + const settingsPage = new SettingsPage(page, testInfo, apiRequests); + const metaCheckbox = settingsPage.getCheckboxBySetting( + 'Disable description meta tag', + ); const isChecked = await metaCheckbox.isChecked(); - if ( ! isChecked ) { - await settingsPage.toggleSetting( 'Disable description meta tag' ); + if (!isChecked) { + await settingsPage.toggleSetting('Disable description meta tag'); } - await page.goto( '/' ); + await page.goto('/'); - const metaDescription = page.locator( 'meta[name="description"]' ); - await expect( metaDescription ).not.toBeAttached(); - } ); -} ); + const metaDescription = page.locator('meta[name="description"]'); + await expect(metaDescription).not.toBeAttached(); + }); +}); diff --git a/tests/playwright/tests/settings/header-footer.test.ts b/tests/playwright/tests/settings/header-footer.test.ts index 1450bf40..e6c4ee4b 100644 --- a/tests/playwright/tests/settings/header-footer.test.ts +++ b/tests/playwright/tests/settings/header-footer.test.ts @@ -2,47 +2,57 @@ import { parallelTest as test } from '../../parallelTest.ts'; import { expect } from '@playwright/test'; import SettingsPage from '../../pages/settings-page.ts'; -test.describe.serial( 'Header and Footer Setting - Behavior Tests', () => { - test.beforeEach( async ( { page, apiRequests }, testInfo ) => { - const settingsPage = new SettingsPage( page, testInfo, apiRequests ); +test.describe.serial('Header and Footer Setting - Behavior Tests', () => { + test.beforeEach(async ({ page, apiRequests }, testInfo) => { + const settingsPage = new SettingsPage(page, testInfo, apiRequests); await settingsPage.gotoSettingsPage(); await settingsPage.waitForPageLoad(); - await settingsPage.clickTab( 'Structure and layout' ); - } ); - - test( 'should show header and footer by default when setting is disabled', async ( { page, apiRequests }, testInfo ) => { - const settingsPage = new SettingsPage( page, testInfo, apiRequests ); - const headerFooterCheckbox = settingsPage.getCheckboxBySetting( 'Disable theme header and footer' ); + await settingsPage.clickTab('Structure and layout'); + }); + + test('should show header and footer by default when setting is disabled', async ({ + page, + apiRequests, + }, testInfo) => { + const settingsPage = new SettingsPage(page, testInfo, apiRequests); + const headerFooterCheckbox = settingsPage.getCheckboxBySetting( + 'Disable theme header and footer', + ); const isChecked = await headerFooterCheckbox.isChecked(); - if ( isChecked ) { - await settingsPage.toggleSetting( 'Disable theme header and footer' ); + if (isChecked) { + await settingsPage.toggleSetting('Disable theme header and footer'); } - await page.goto( '/' ); + await page.goto('/'); - const header = page.locator( 'header#site-header.site-header' ); - const footer = page.locator( 'footer#site-footer.site-footer' ); + const header = page.locator('header#site-header.site-header'); + const footer = page.locator('footer#site-footer.site-footer'); - await expect( header ).toBeAttached(); - await expect( footer ).toBeAttached(); - } ); + await expect(header).toBeAttached(); + await expect(footer).toBeAttached(); + }); - test( 'should hide header and footer when setting is enabled', async ( { page, apiRequests }, testInfo ) => { - const settingsPage = new SettingsPage( page, testInfo, apiRequests ); + test('should hide header and footer when setting is enabled', async ({ + page, + apiRequests, + }, testInfo) => { + const settingsPage = new SettingsPage(page, testInfo, apiRequests); - const headerFooterCheckbox = settingsPage.getCheckboxBySetting( 'Disable theme header and footer' ); + const headerFooterCheckbox = settingsPage.getCheckboxBySetting( + 'Disable theme header and footer', + ); const isChecked = await headerFooterCheckbox.isChecked(); - if ( ! isChecked ) { - await settingsPage.toggleSetting( 'Disable theme header and footer' ); + if (!isChecked) { + await settingsPage.toggleSetting('Disable theme header and footer'); } - await page.goto( '/' ); + await page.goto('/'); - const header = page.locator( 'header#site-header.site-header' ); - const footer = page.locator( 'footer#site-footer.site-footer' ); + const header = page.locator('header#site-header.site-header'); + const footer = page.locator('footer#site-footer.site-footer'); - await expect( header ).not.toBeAttached(); - await expect( footer ).not.toBeAttached(); - } ); -} ); + await expect(header).not.toBeAttached(); + await expect(footer).not.toBeAttached(); + }); +}); diff --git a/tests/playwright/tests/settings/page-title.test.ts b/tests/playwright/tests/settings/page-title.test.ts index b740e73d..b498975b 100644 --- a/tests/playwright/tests/settings/page-title.test.ts +++ b/tests/playwright/tests/settings/page-title.test.ts @@ -2,48 +2,56 @@ import { parallelTest as test } from '../../parallelTest.ts'; import { expect } from '@playwright/test'; import SettingsPage from '../../pages/settings-page.ts'; -test.describe.serial( 'Page Title Setting - Behavior Tests', () => { +test.describe.serial('Page Title Setting - Behavior Tests', () => { let postId: string; - test.beforeEach( async ( { page, apiRequests }, testInfo ) => { - const settingsPage = new SettingsPage( page, testInfo, apiRequests ); + test.beforeEach(async ({ page, apiRequests }, testInfo) => { + const settingsPage = new SettingsPage(page, testInfo, apiRequests); postId = await settingsPage.createNewBasicPost(); await settingsPage.gotoSettingsPage(); await settingsPage.waitForPageLoad(); - await settingsPage.clickTab( 'Structure and layout' ); - } ); + await settingsPage.clickTab('Structure and layout'); + }); - test( 'should hide page title when setting is checked', async ( { page, apiRequests, request }, testInfo ) => { - const settingsPage = new SettingsPage( page, testInfo, apiRequests ); - const pageTitleCheckbox = settingsPage.getCheckboxBySetting( 'Hide page title' ); + test('should hide page title when setting is checked', async ({ + page, + apiRequests, + }, testInfo) => { + const settingsPage = new SettingsPage(page, testInfo, apiRequests); + const pageTitleCheckbox = + settingsPage.getCheckboxBySetting('Hide page title'); const isChecked = await pageTitleCheckbox.isChecked(); - if ( ! isChecked ) { - await settingsPage.toggleSetting( 'Hide page title' ); + if (!isChecked) { + await settingsPage.toggleSetting('Hide page title'); } // Act - Navigate to the test post - await page.goto( `/?p=${ postId }` ); + await page.goto(`/?p=${postId}`); // Assert - Page title should be hidden - const pageHeader = page.locator( '.page-header h1.entry-title' ); - await expect( pageHeader ).not.toBeAttached(); - } ); - - test( 'should show page title when setting is unchecked', async ( { page, apiRequests, request }, testInfo ) => { - const settingsPage = new SettingsPage( page, testInfo, apiRequests ); - const pageTitleCheckbox = settingsPage.getCheckboxBySetting( 'Hide page title' ); + const pageHeader = page.locator('.page-header h1.entry-title'); + await expect(pageHeader).not.toBeAttached(); + }); + + test('should show page title when setting is unchecked', async ({ + page, + apiRequests, + }, testInfo) => { + const settingsPage = new SettingsPage(page, testInfo, apiRequests); + const pageTitleCheckbox = + settingsPage.getCheckboxBySetting('Hide page title'); const isChecked = await pageTitleCheckbox.isChecked(); - if ( isChecked ) { - await settingsPage.toggleSetting( 'Hide page title' ); + if (isChecked) { + await settingsPage.toggleSetting('Hide page title'); } // Act - Navigate to the test post - await page.goto( `/?p=${ postId }` ); + await page.goto(`/?p=${postId}`); // Assert - Page title should be visible - const pageHeader = page.locator( '.page-header h1.entry-title' ); - await expect( pageHeader ).toBeAttached(); - await expect( pageHeader ).toHaveText( 'Playwright Test Page #' + postId ); - } ); -} ); + const pageHeader = page.locator('.page-header h1.entry-title'); + await expect(pageHeader).toBeAttached(); + await expect(pageHeader).toHaveText('Playwright Test Page #' + postId); + }); +}); diff --git a/tests/playwright/tests/settings/reset-css.test .ts b/tests/playwright/tests/settings/reset-css.test .ts index 73fddc61..c9666e93 100644 --- a/tests/playwright/tests/settings/reset-css.test .ts +++ b/tests/playwright/tests/settings/reset-css.test .ts @@ -2,69 +2,85 @@ import { parallelTest as test } from '../../parallelTest.ts'; import { expect } from '@playwright/test'; import SettingsPage from '../../pages/settings-page.ts'; -test.describe.serial( 'Reset CSS Setting - Behavior Tests', () => { - test.beforeEach( async ( { page, apiRequests }, testInfo ) => { - const settingsPage = new SettingsPage( page, testInfo, apiRequests ); +test.describe.serial('Reset CSS Setting - Behavior Tests', () => { + test.beforeEach(async ({ page, apiRequests }, testInfo) => { + const settingsPage = new SettingsPage(page, testInfo, apiRequests); await settingsPage.gotoSettingsPage(); await settingsPage.waitForPageLoad(); - await settingsPage.clickTab( 'CSS and styling control' ); - } ); + await settingsPage.clickTab('CSS and styling control'); + }); - test( 'should load reset.css by default when setting is disabled', async ( { page, apiRequests }, testInfo ) => { - const settingsPage = new SettingsPage( page, testInfo, apiRequests ); - const themeCssCheckbox = settingsPage.getCheckboxBySetting( 'Deregister Hello reset.css' ); + test('should load reset.css by default when setting is disabled', async ({ + page, + apiRequests, + }, testInfo) => { + const settingsPage = new SettingsPage(page, testInfo, apiRequests); + const themeCssCheckbox = settingsPage.getCheckboxBySetting( + 'Deregister Hello reset.css', + ); const isChecked = await themeCssCheckbox.isChecked(); - if ( isChecked ) { - await settingsPage.toggleSetting( 'Deregister Hello reset.css' ); + if (isChecked) { + await settingsPage.toggleSetting('Deregister Hello reset.css'); } const cssRequests: string[] = []; - page.on( 'request', ( request ) => { - if ( request.url().includes( '.css' ) ) { - cssRequests.push( request.url() ); + page.on('request', (request) => { + if (request.url().includes('.css')) { + cssRequests.push(request.url()); } - } ); + }); - await page.goto( '/' ); + await page.goto('/'); - const themeCssLoaded = cssRequests.some( ( url ) => - url.includes( '/hello-theme/assets/css/reset.css' ) || - url.includes( '/hello-elementor/assets/css/reset.css' ), + const themeCssLoaded = cssRequests.some( + (url) => + url.includes('/hello-theme/assets/css/reset.css') || + url.includes('/hello-elementor/assets/css/reset.css'), ); - expect( themeCssLoaded ).toBeTruthy(); + expect(themeCssLoaded).toBeTruthy(); - const themeCssLink = page.locator( 'link[rel="stylesheet"][href*="reset.css"]' ).first(); - await expect( themeCssLink ).toBeAttached(); - } ); + const themeCssLink = page + .locator('link[rel="stylesheet"][href*="reset.css"]') + .first(); + await expect(themeCssLink).toBeAttached(); + }); - test( 'should not load reset.css when setting is enabled', async ( { page, apiRequests }, testInfo ) => { - const settingsPage = new SettingsPage( page, testInfo, apiRequests ); + test('should not load reset.css when setting is enabled', async ({ + page, + apiRequests, + }, testInfo) => { + const settingsPage = new SettingsPage(page, testInfo, apiRequests); - const themeCssCheckbox = settingsPage.getCheckboxBySetting( 'Deregister Hello reset.css' ); + const themeCssCheckbox = settingsPage.getCheckboxBySetting( + 'Deregister Hello reset.css', + ); const isChecked = await themeCssCheckbox.isChecked(); - if ( ! isChecked ) { - await settingsPage.toggleSetting( 'Deregister Hello reset.css' ); + if (!isChecked) { + await settingsPage.toggleSetting('Deregister Hello reset.css'); } const cssRequests: string[] = []; - page.on( 'request', ( request ) => { - if ( request.url().includes( '.css' ) ) { - cssRequests.push( request.url() ); + page.on('request', (request) => { + if (request.url().includes('.css')) { + cssRequests.push(request.url()); } - } ); + }); - await page.goto( '/' ); + await page.goto('/'); - const themeCssLoaded = cssRequests.some( ( url ) => - url.includes( '/hello-theme/assets/css/reset.css' ) || - url.includes( '/hello-elementor/assets/css/reset.css' ), + const themeCssLoaded = cssRequests.some( + (url) => + url.includes('/hello-theme/assets/css/reset.css') || + url.includes('/hello-elementor/assets/css/reset.css'), ); - expect( themeCssLoaded ).toBeFalsy(); + expect(themeCssLoaded).toBeFalsy(); - const themeCssLink = page.locator( 'link[rel="stylesheet"][href*="reset.css"]' ).first(); - await expect( themeCssLink ).not.toBeAttached(); - } ); -} ); + const themeCssLink = page + .locator('link[rel="stylesheet"][href*="reset.css"]') + .first(); + await expect(themeCssLink).not.toBeAttached(); + }); +}); diff --git a/tests/playwright/tests/settings/settings-suite.test.ts b/tests/playwright/tests/settings/settings-suite.test.ts index 0d00a831..0ce61d30 100644 --- a/tests/playwright/tests/settings/settings-suite.test.ts +++ b/tests/playwright/tests/settings/settings-suite.test.ts @@ -2,9 +2,12 @@ import { parallelTest as test } from '../../parallelTest.ts'; import { expect } from '@playwright/test'; import SettingsPage from '../../pages/settings-page.ts'; -test.describe.serial( 'Hello Biz Settings - Complete Test Suite', () => { - test( 'should verify all settings tabs are accessible', async ( { page, apiRequests }, testInfo ) => { - const settingsPage = new SettingsPage( page, testInfo, apiRequests ); +test.describe.serial('Hello Biz Settings - Complete Test Suite', () => { + test('should verify all settings tabs are accessible', async ({ + page, + apiRequests, + }, testInfo) => { + const settingsPage = new SettingsPage(page, testInfo, apiRequests); await settingsPage.gotoSettingsPage(); await settingsPage.waitForPageLoad(); @@ -15,59 +18,69 @@ test.describe.serial( 'Hello Biz Settings - Complete Test Suite', () => { 'CSS and styling control', ]; - for ( const tabName of tabs ) { - const tab = settingsPage.getTab( tabName ); - await expect( tab ).toBeVisible(); + for (const tabName of tabs) { + const tab = settingsPage.getTab(tabName); + await expect(tab).toBeVisible(); - await settingsPage.clickTab( tabName ); - const panel = settingsPage.getTabPanel( tabName ); - await expect( panel ).toBeVisible(); + await settingsPage.clickTab(tabName); + const panel = settingsPage.getTabPanel(tabName); + await expect(panel).toBeVisible(); } - } ); + }); - test( 'should toggle settings correctly', async ( { page, apiRequests }, testInfo ) => { - const settingsPage = new SettingsPage( page, testInfo, apiRequests ); + test('should toggle settings correctly', async ({ + page, + apiRequests, + }, testInfo) => { + const settingsPage = new SettingsPage(page, testInfo, apiRequests); await settingsPage.gotoSettingsPage(); await settingsPage.waitForPageLoad(); - await settingsPage.clickTab( 'SEO and accessibility' ); - const skipLinksCheckbox = settingsPage.getCheckboxBySetting( 'Disable skip links' ); + await settingsPage.clickTab('SEO and accessibility'); + const skipLinksCheckbox = + settingsPage.getCheckboxBySetting('Disable skip links'); const initialSkipLinksState = await skipLinksCheckbox.isChecked(); - await settingsPage.toggleSetting( 'Disable skip links' ); + await settingsPage.toggleSetting('Disable skip links'); const newSkipLinksState = await skipLinksCheckbox.isChecked(); - expect( newSkipLinksState ).toBe( ! initialSkipLinksState ); + expect(newSkipLinksState).toBe(!initialSkipLinksState); - await settingsPage.clickTab( 'Structure and layout' ); - const headerFooterCheckbox = settingsPage.getCheckboxBySetting( 'Disable theme header and footer' ); + await settingsPage.clickTab('Structure and layout'); + const headerFooterCheckbox = settingsPage.getCheckboxBySetting( + 'Disable theme header and footer', + ); const initialHeaderFooterState = await headerFooterCheckbox.isChecked(); - await settingsPage.toggleSetting( 'Disable theme header and footer' ); + await settingsPage.toggleSetting('Disable theme header and footer'); const newHeaderFooterState = await headerFooterCheckbox.isChecked(); - expect( newHeaderFooterState ).toBe( ! initialHeaderFooterState ); - } ); - - test( 'should persist settings across page reloads', async ( { page, apiRequests }, testInfo ) => { - const settingsPage = new SettingsPage( page, testInfo, apiRequests ); + expect(newHeaderFooterState).toBe(!initialHeaderFooterState); + }); + + test('should persist settings across page reloads', async ({ + page, + apiRequests, + }, testInfo) => { + const settingsPage = new SettingsPage(page, testInfo, apiRequests); await settingsPage.gotoSettingsPage(); await settingsPage.waitForPageLoad(); - await settingsPage.clickTab( 'SEO and accessibility' ); - const skipLinksCheckbox = settingsPage.getCheckboxBySetting( 'Disable skip links' ); + await settingsPage.clickTab('SEO and accessibility'); + const skipLinksCheckbox = + settingsPage.getCheckboxBySetting('Disable skip links'); const initialState = await skipLinksCheckbox.isChecked(); - await settingsPage.toggleSetting( 'Disable skip links' ); + await settingsPage.toggleSetting('Disable skip links'); const stateAfterToggle = await skipLinksCheckbox.isChecked(); await page.reload(); await settingsPage.waitForPageLoad(); - await settingsPage.clickTab( 'SEO and accessibility' ); + await settingsPage.clickTab('SEO and accessibility'); const stateAfterReload = await skipLinksCheckbox.isChecked(); - expect( stateAfterReload ).toBe( stateAfterToggle ); + expect(stateAfterReload).toBe(stateAfterToggle); - await settingsPage.toggleSetting( 'Disable skip links' ); + await settingsPage.toggleSetting('Disable skip links'); const finalState = await skipLinksCheckbox.isChecked(); - expect( finalState ).toBe( initialState ); - } ); -} ); + expect(finalState).toBe(initialState); + }); +}); diff --git a/tests/playwright/tests/settings/skip-links.test.ts b/tests/playwright/tests/settings/skip-links.test.ts index c863c468..f1a79a6a 100644 --- a/tests/playwright/tests/settings/skip-links.test.ts +++ b/tests/playwright/tests/settings/skip-links.test.ts @@ -2,62 +2,80 @@ import { parallelTest as test } from '../../parallelTest.ts'; import { expect } from '@playwright/test'; import SettingsPage from '../../pages/settings-page.ts'; -test.describe.serial( 'Skip Links Setting - Behavior Tests', () => { - test.beforeEach( async ( { page, apiRequests }, testInfo ) => { - const settingsPage = new SettingsPage( page, testInfo, apiRequests ); +test.describe.serial('Skip Links Setting - Behavior Tests', () => { + test.beforeEach(async ({ page, apiRequests }, testInfo) => { + const settingsPage = new SettingsPage(page, testInfo, apiRequests); await settingsPage.gotoSettingsPage(); await settingsPage.waitForPageLoad(); - await settingsPage.clickTab( 'SEO and accessibility' ); - } ); + await settingsPage.clickTab('SEO and accessibility'); + }); - test( 'should show skip link by default when setting is disabled', async ( { page, apiRequests }, testInfo ) => { - const settingsPage = new SettingsPage( page, testInfo, apiRequests ); - const skipLinkCheckbox = settingsPage.getCheckboxBySetting( 'Disable skip links' ); + test('should show skip link by default when setting is disabled', async ({ + page, + apiRequests, + }, testInfo) => { + const settingsPage = new SettingsPage(page, testInfo, apiRequests); + const skipLinkCheckbox = + settingsPage.getCheckboxBySetting('Disable skip links'); const isChecked = await skipLinkCheckbox.isChecked(); - if ( isChecked ) { - await settingsPage.toggleSetting( 'Disable skip links' ); + if (isChecked) { + await settingsPage.toggleSetting('Disable skip links'); } - await page.goto( '/' ); + await page.goto('/'); - const skipLink = page.locator( 'a.skip-link.screen-reader-text[href="#content"]' ); - await expect( skipLink ).toBeAttached(); - await expect( skipLink ).toHaveText( 'Skip to content' ); - } ); + const skipLink = page.locator( + 'a.skip-link.screen-reader-text[href="#content"]', + ); + await expect(skipLink).toBeAttached(); + await expect(skipLink).toHaveText('Skip to content'); + }); - test( 'should hide skip link when setting is enabled', async ( { page, apiRequests }, testInfo ) => { - const settingsPage = new SettingsPage( page, testInfo, apiRequests ); + test('should hide skip link when setting is enabled', async ({ + page, + apiRequests, + }, testInfo) => { + const settingsPage = new SettingsPage(page, testInfo, apiRequests); - const skipLinkCheckbox = settingsPage.getCheckboxBySetting( 'Disable skip links' ); + const skipLinkCheckbox = + settingsPage.getCheckboxBySetting('Disable skip links'); const isChecked = await skipLinkCheckbox.isChecked(); - if ( ! isChecked ) { - await settingsPage.toggleSetting( 'Disable skip links' ); + if (!isChecked) { + await settingsPage.toggleSetting('Disable skip links'); } - await page.goto( '/' ); + await page.goto('/'); - const skipLink = page.locator( 'a.skip-link.screen-reader-text[href="#content"]' ); - await expect( skipLink ).not.toBeAttached(); - } ); + const skipLink = page.locator( + 'a.skip-link.screen-reader-text[href="#content"]', + ); + await expect(skipLink).not.toBeAttached(); + }); - test( 'should verify skip link functionality when visible', async ( { page, apiRequests }, testInfo ) => { - const settingsPage = new SettingsPage( page, testInfo, apiRequests ); + test('should verify skip link functionality when visible', async ({ + page, + apiRequests, + }, testInfo) => { + const settingsPage = new SettingsPage(page, testInfo, apiRequests); - const skipLinkCheckbox = settingsPage.getCheckboxBySetting( 'Disable skip links' ); + const skipLinkCheckbox = + settingsPage.getCheckboxBySetting('Disable skip links'); const isChecked = await skipLinkCheckbox.isChecked(); - if ( isChecked ) { - await settingsPage.toggleSetting( 'Disable skip links' ); + if (isChecked) { + await settingsPage.toggleSetting('Disable skip links'); } - await page.goto( '/' ); + await page.goto('/'); - const skipLink = page.locator( 'a.skip-link.screen-reader-text[href="#content"]' ); - await expect( skipLink ).toBeAttached(); + const skipLink = page.locator( + 'a.skip-link.screen-reader-text[href="#content"]', + ); + await expect(skipLink).toBeAttached(); await skipLink.focus(); await skipLink.click(); - await expect( page ).toHaveURL( /#content$/ ); - } ); -} ); + await expect(page).toHaveURL(/#content$/); + }); +}); diff --git a/tests/playwright/tests/settings/theme-css.test.ts b/tests/playwright/tests/settings/theme-css.test.ts index 82c4d612..ea83302d 100644 --- a/tests/playwright/tests/settings/theme-css.test.ts +++ b/tests/playwright/tests/settings/theme-css.test.ts @@ -2,69 +2,85 @@ import { parallelTest as test } from '../../parallelTest.ts'; import { expect } from '@playwright/test'; import SettingsPage from '../../pages/settings-page.ts'; -test.describe.serial( 'Theme CSS Setting - Behavior Tests', () => { - test.beforeEach( async ( { page, apiRequests }, testInfo ) => { - const settingsPage = new SettingsPage( page, testInfo, apiRequests ); +test.describe.serial('Theme CSS Setting - Behavior Tests', () => { + test.beforeEach(async ({ page, apiRequests }, testInfo) => { + const settingsPage = new SettingsPage(page, testInfo, apiRequests); await settingsPage.gotoSettingsPage(); await settingsPage.waitForPageLoad(); - await settingsPage.clickTab( 'CSS and styling control' ); - } ); + await settingsPage.clickTab('CSS and styling control'); + }); - test( 'should load theme.css by default when setting is disabled', async ( { page, apiRequests }, testInfo ) => { - const settingsPage = new SettingsPage( page, testInfo, apiRequests ); - const themeCssCheckbox = settingsPage.getCheckboxBySetting( 'Deregister Hello theme.css' ); + test('should load theme.css by default when setting is disabled', async ({ + page, + apiRequests, + }, testInfo) => { + const settingsPage = new SettingsPage(page, testInfo, apiRequests); + const themeCssCheckbox = settingsPage.getCheckboxBySetting( + 'Deregister Hello theme.css', + ); const isChecked = await themeCssCheckbox.isChecked(); - if ( isChecked ) { - await settingsPage.toggleSetting( 'Deregister Hello theme.css' ); + if (isChecked) { + await settingsPage.toggleSetting('Deregister Hello theme.css'); } const cssRequests: string[] = []; - page.on( 'request', ( request ) => { - if ( request.url().includes( '.css' ) ) { - cssRequests.push( request.url() ); + page.on('request', (request) => { + if (request.url().includes('.css')) { + cssRequests.push(request.url()); } - } ); + }); - await page.goto( '/' ); + await page.goto('/'); - const themeCssLoaded = cssRequests.some( ( url ) => - url.includes( '/hello-theme/assets/css/theme.css' ) || - url.includes( '/hello-elementor/assets/css/theme.css' ), + const themeCssLoaded = cssRequests.some( + (url) => + url.includes('/hello-theme/assets/css/theme.css') || + url.includes('/hello-elementor/assets/css/theme.css'), ); - expect( themeCssLoaded ).toBeTruthy(); + expect(themeCssLoaded).toBeTruthy(); - const themeCssLink = page.locator( 'link[rel="stylesheet"][href*="theme.css"]' ).first(); - await expect( themeCssLink ).toBeAttached(); - } ); + const themeCssLink = page + .locator('link[rel="stylesheet"][href*="theme.css"]') + .first(); + await expect(themeCssLink).toBeAttached(); + }); - test( 'should not load theme.css when setting is enabled', async ( { page, apiRequests }, testInfo ) => { - const settingsPage = new SettingsPage( page, testInfo, apiRequests ); + test('should not load theme.css when setting is enabled', async ({ + page, + apiRequests, + }, testInfo) => { + const settingsPage = new SettingsPage(page, testInfo, apiRequests); - const themeCssCheckbox = settingsPage.getCheckboxBySetting( 'Deregister Hello theme.css' ); + const themeCssCheckbox = settingsPage.getCheckboxBySetting( + 'Deregister Hello theme.css', + ); const isChecked = await themeCssCheckbox.isChecked(); - if ( ! isChecked ) { - await settingsPage.toggleSetting( 'Deregister Hello theme.css' ); + if (!isChecked) { + await settingsPage.toggleSetting('Deregister Hello theme.css'); } const cssRequests: string[] = []; - page.on( 'request', ( request ) => { - if ( request.url().includes( '.css' ) ) { - cssRequests.push( request.url() ); + page.on('request', (request) => { + if (request.url().includes('.css')) { + cssRequests.push(request.url()); } - } ); + }); - await page.goto( '/' ); + await page.goto('/'); - const themeCssLoaded = cssRequests.some( ( url ) => - url.includes( '/hello-theme/assets/css/theme.css' ) || - url.includes( '/hello-elementor/assets/css/theme.css' ), + const themeCssLoaded = cssRequests.some( + (url) => + url.includes('/hello-theme/assets/css/theme.css') || + url.includes('/hello-elementor/assets/css/theme.css'), ); - expect( themeCssLoaded ).toBeFalsy(); + expect(themeCssLoaded).toBeFalsy(); - const themeCssLink = page.locator( 'link[rel="stylesheet"][href*="theme.css"]' ).first(); - await expect( themeCssLink ).not.toBeAttached(); - } ); -} ); + const themeCssLink = page + .locator('link[rel="stylesheet"][href*="theme.css"]') + .first(); + await expect(themeCssLink).not.toBeAttached(); + }); +}); diff --git a/tests/playwright/tests/theme-settings.test.ts b/tests/playwright/tests/theme-settings.test.ts index e6f6fe1f..b32539a8 100644 --- a/tests/playwright/tests/theme-settings.test.ts +++ b/tests/playwright/tests/theme-settings.test.ts @@ -2,22 +2,25 @@ import { parallelTest as test } from '../parallelTest.ts'; import { expect } from '@playwright/test'; import WpAdminPage from '../pages/wp-admin-page.ts'; -test.describe( 'Admin Menu', () => { - test( 'Hello Elementor menu exists in sidebar with correct name', async ( { page, apiRequests }, testInfo ) => { - // Arrange - const wpAdmin = new WpAdminPage( page, testInfo, apiRequests ); +test.describe('Admin Menu', () => { + test('Hello Elementor menu exists in sidebar with correct name', async ({ + page, + apiRequests, + }, testInfo) => { + // Arrange + const wpAdmin = new WpAdminPage(page, testInfo, apiRequests); - // Navigate to dashboard - await wpAdmin.gotoDashboard(); + // Navigate to dashboard + await wpAdmin.gotoDashboard(); - // Get the Hello Elementor menu element - const helloElementorMenu = page.locator( '#toplevel_page_hello-elementor' ); + // Get the Hello Elementor menu element + const helloElementorMenu = page.locator('#toplevel_page_hello-elementor'); - // Verify the menu exists - await expect( helloElementorMenu ).toBeVisible(); + // Verify the menu exists + await expect(helloElementorMenu).toBeVisible(); - // Verify the menu has the correct title "Hello Elementor" - const menuTitle = helloElementorMenu.locator( '.wp-menu-name' ); - await expect( menuTitle ).toHaveText( 'Hello' ); - } ); -} ); + // Verify the menu has the correct title "Hello Elementor" + const menuTitle = helloElementorMenu.locator('.wp-menu-name'); + await expect(menuTitle).toHaveText('Hello'); + }); +}); diff --git a/tests/playwright/types/types.ts b/tests/playwright/types/types.ts index c30ff589..2e1dc69c 100644 --- a/tests/playwright/types/types.ts +++ b/tests/playwright/types/types.ts @@ -1,77 +1,87 @@ export type Image = { - title: string, - description?: string, - altText?: string, - caption?: string, - extension: string, - filePath?: string -} + title: string; + description?: string; + altText?: string; + caption?: string; + extension: string; + filePath?: string; +}; export type User = { - id?: string, - username: string, - password: string, - email: string, - roles?: string[], -} + id?: string; + username: string; + password: string; + email: string; + roles?: string[]; +}; export type LinkOptions = { - targetBlank?: boolean, - noFollow?: boolean, - customAttributes?: {key:string, value: string }, - linkTo?: boolean, - linkInpSelector?: string -} + targetBlank?: boolean; + noFollow?: boolean; + customAttributes?: { key: string; value: string }; + linkTo?: boolean; + linkInpSelector?: string; +}; export type WpPage = { title: { - rendered?: string, - } - date?: string, - dateGmt?: string, - guid?: string, - id?: string, - link?: string, - modified?: string, - modifiedGmt?: string, - slug: string, - status?: 'publish' | 'future' | 'draft' | 'pending' | 'private', - type?: string, - password?: string, - permalinkTemplate?: string, - generatedSlug?: string, - parent?: string, - content: string, - author?: string, - excerpt?: string, - featuredMedia?: string, - commentStatus?: string, - pingStatus?: string, - menuOrder?: string, - meta?: string, - template?: string, -} + rendered?: string; + }; + date?: string; + dateGmt?: string; + guid?: string; + id?: string; + link?: string; + modified?: string; + modifiedGmt?: string; + slug: string; + status?: 'publish' | 'future' | 'draft' | 'pending' | 'private'; + type?: string; + password?: string; + permalinkTemplate?: string; + generatedSlug?: string; + parent?: string; + content: string; + author?: string; + excerpt?: string; + featuredMedia?: string; + commentStatus?: string; + pingStatus?: string; + menuOrder?: string; + meta?: string; + template?: string; +}; export type Post = { - id?: string, - date?: string, - dateGmt?: string, - slug?: string, - status?: 'publish' | 'future' | 'draft' | 'pending' | 'private', - password?: string, - title?: string, - content?: string, - author?: number, - excerpt?: string, - featuredMedia?: number, - commentStatus?: 'open' | 'closed', - pingStatus?: 'open' | 'closed', - format?: 'standard' | 'aside' | 'chat' | 'gallery' | 'link' | 'image' | 'quote' | 'status' | 'video' | 'audio', - meta?: string, - sticky?: boolean, - template?: string, - tags?: number -} + id?: string; + date?: string; + dateGmt?: string; + slug?: string; + status?: 'publish' | 'future' | 'draft' | 'pending' | 'private'; + password?: string; + title?: string; + content?: string; + author?: number; + excerpt?: string; + featuredMedia?: number; + commentStatus?: 'open' | 'closed'; + pingStatus?: 'open' | 'closed'; + format?: + | 'standard' + | 'aside' + | 'chat' + | 'gallery' + | 'link' + | 'image' + | 'quote' + | 'status' + | 'video' + | 'audio'; + meta?: string; + sticky?: boolean; + template?: string; + tags?: number; +}; export type PageData = { id: string; @@ -80,45 +90,54 @@ export type PageData = { export type WindowType = Window & { $e?: { - run: ( s: string, o: object )=> unknown - } - wpApiSettings?: { nonce: string } + run: (s: string, o: object) => unknown; + }; + wpApiSettings?: { nonce: string }; elementorNotifications?: { - destroy: () => void - }, - formWasSubmitted?: boolean - formErrorDetected?: boolean + destroy: () => void; + }; + formWasSubmitted?: boolean; + formErrorDetected?: boolean; }; export type BackboneType = { - Model: new ( o: {title: string} )=> unknown + Model: new (o: { title: string }) => unknown; }; export type $eType = { - run: ( s: string, o: object )=> unknown -} + run: (s: string, o: object) => unknown; +}; export type ElementorType = { navigator?: { - isOpen: ()=> unknown - }, - getContainer?: ( id: string )=> unknown, + isOpen: () => unknown; + }; + getContainer?: (id: string) => unknown; config?: { - initialDocument:{ - id: string - } - }, - isDeviceModeActive?: () => unknown -} + initialDocument: { + id: string; + }; + }; + isDeviceModeActive?: () => unknown; +}; -export type Device = 'mobile' | 'mobile_extra' | 'tablet' | 'tablet_extra' | 'laptop' | 'desktop' | 'widescreen'; +export type Device = + | 'mobile' + | 'mobile_extra' + | 'tablet' + | 'tablet_extra' + | 'laptop' + | 'desktop' + | 'widescreen'; export type BreakpointEditableDevice = Exclude; -export type GapControl = string | { - column: string, - row: string, - unit?: string -} +export type GapControl = + | string + | { + column: string; + row: string; + unit?: string; + }; export type ContainerType = 'flex' | 'grid'; @@ -134,4 +153,4 @@ export type ContainerPreset = | 'c100-c50-50' | '33-33-33-33-33-33' | '33-33-33-33-66' - | '66-33-33-66' + | '66-33-33-66'; diff --git a/tests/playwright/utils/test-page-url.ts b/tests/playwright/utils/test-page-url.ts index 9d817ad5..73334f35 100644 --- a/tests/playwright/utils/test-page-url.ts +++ b/tests/playwright/utils/test-page-url.ts @@ -2,7 +2,7 @@ import fs from 'fs'; import path from 'path'; // File to store page URL -const urlStorePath = path.join( __dirname, '..', 'temp-form-page-url.txt' ); +const urlStorePath = path.join(__dirname, '..', 'temp-form-page-url.txt'); /** * Gets the page URL from the file storage @@ -11,8 +11,8 @@ const urlStorePath = path.join( __dirname, '..', 'temp-form-page-url.txt' ); */ export const getPageUrl = (): string => { try { - return fs.readFileSync( urlStorePath, 'utf8' ); - } catch ( e ) { + return fs.readFileSync(urlStorePath, 'utf8'); + } catch { return ''; } }; @@ -22,6 +22,6 @@ export const getPageUrl = (): string => { * * @param {string} url - The URL to save */ -export const savePageUrl = ( url: string ): void => { - fs.writeFileSync( urlStorePath, url ); +export const savePageUrl = (url: string): void => { + fs.writeFileSync(urlStorePath, url); }; diff --git a/tests/playwright/wp-authentication.ts b/tests/playwright/wp-authentication.ts index 687dbe8b..31ac3ece 100644 --- a/tests/playwright/wp-authentication.ts +++ b/tests/playwright/wp-authentication.ts @@ -1,60 +1,74 @@ -import { APIRequest, APIRequestContext, Page, chromium, APIResponse } from '@playwright/test'; +import { + APIRequest, + APIRequestContext, + Page, + chromium, + APIResponse, +} from '@playwright/test'; -export async function login( apiRequest: APIRequest, user: string, password: string, baseUrl: string ) { +export async function login( + apiRequest: APIRequest, + user: string, + password: string, + baseUrl: string, +) { // Important: make sure we authenticate in a clean environment by unsetting storage state. - const context = await apiRequest.newContext( { storageState: undefined } ); + const context = await apiRequest.newContext({ storageState: undefined }); - await context.post( `${ baseUrl }/wp-login.php`, { + await context.post(`${baseUrl}/wp-login.php`, { form: { log: user, pwd: password, 'wp-submit': 'Log In', - redirect_to: `${ baseUrl }/wp-admin/`, + redirect_to: `${baseUrl}/wp-admin/`, testcookie: '1', }, - } ); + }); return context; } -export async function fetchNonce( context: APIRequestContext, baseUrl: string ) { - const response = await context.get( `${ baseUrl }/wp-admin/post-new.php` ); +export async function fetchNonce(context: APIRequestContext, baseUrl: string) { + const response = await context.get(`${baseUrl}/wp-admin/post-new.php`); - await validateResponse( response, 'Failed to fetch page' ); + await validateResponse(response, 'Failed to fetch page'); let pageText = await response.text(); - if ( pageText.includes( 'WordPress has been updated!' ) ) { - pageText = await updateDatabase( context, baseUrl ); + if (pageText.includes('WordPress has been updated!')) { + pageText = await updateDatabase(context, baseUrl); } - const nonceMatch = pageText.match( /var wpApiSettings = .*;/ ); - if ( ! nonceMatch ) { - throw new Error( `Nonce not found on the page:\n"${ pageText }"` ); + const nonceMatch = pageText.match(/var wpApiSettings = .*;/); + if (!nonceMatch) { + throw new Error(`Nonce not found on the page:\n"${pageText}"`); } - return nonceMatch[ 0 ].replace( /^.*"nonce":"([^"]*)".*$/, '$1' ); + return nonceMatch[0].replace(/^.*"nonce":"([^"]*)".*$/, '$1'); } -async function updateDatabase( context: APIRequestContext, baseUrl: string ): Promise { +async function updateDatabase( + context: APIRequestContext, + baseUrl: string, +): Promise { const browser = await chromium.launch(); const browserContext = await browser.newContext(); const page: Page = await browserContext.newPage(); - await page.goto( `${ baseUrl }/wp-admin/post-new.php` ); - await page.getByText( 'Update WordPress Database' ).click(); - await page.getByText( 'Continue' ).click(); + await page.goto(`${baseUrl}/wp-admin/post-new.php`); + await page.getByText('Update WordPress Database').click(); + await page.getByText('Continue').click(); - const retryResponse = await context.get( `${ baseUrl }/wp-admin/post-new.php` ); + const retryResponse = await context.get(`${baseUrl}/wp-admin/post-new.php`); const pageText = await retryResponse.text(); await browser.close(); return pageText; } -async function validateResponse( response: APIResponse, errorMessage: string ) { - if ( ! response.ok() ) { - throw new Error( ` - ${ errorMessage }: ${ response.status }. - ${ await response.text() } - ${ response.url() } - ` ); +async function validateResponse(response: APIResponse, errorMessage: string) { + if (!response.ok()) { + throw new Error(` + ${errorMessage}: ${response.status}. + ${await response.text()} + ${response.url()} + `); } } diff --git a/webpack.config.js b/webpack.config.js index 2b2bc690..3fa0e909 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,30 +1,66 @@ // WordPress webpack config: -const defaultConfig = require( '@wordpress/scripts/config/webpack.config' ); +const defaultConfig = require('@wordpress/scripts/config/webpack.config'); //Plugins: -const CopyWebpackPlugin = require( 'copy-webpack-plugin' ); -const RemoveEmptyScriptsPlugin = require( 'webpack-remove-empty-scripts' ); +const CopyWebpackPlugin = require('copy-webpack-plugin'); +const RemoveEmptyScriptsPlugin = require('webpack-remove-empty-scripts'); //Utilities -const path = require( 'path' ); -const imagesPath = path.resolve( __dirname, './assets/images' ); +const path = require('path'); +const imagesPath = path.resolve(__dirname, './assets/images'); const modulesDir = process.cwd() + '/modules/'; const entryPoints = { - 'css/reset': path.resolve( __dirname, './dev/scss', 'reset.scss' ), - 'css/theme': path.resolve( __dirname, './dev/scss', 'theme.scss' ), - 'css/header-footer': path.resolve( __dirname, './dev/scss', 'header-footer.scss' ), - 'css/editor-styles': path.resolve( __dirname, './dev/scss', 'editor-styles.scss' ), - 'css/editor': path.resolve( __dirname, './dev/scss', 'editor.scss' ), - 'css/customizer': path.resolve( __dirname, './dev/scss', 'customizer.scss' ), + 'css/reset': path.resolve(__dirname, './dev/scss', 'reset.scss'), + 'css/theme': path.resolve(__dirname, './dev/scss', 'theme.scss'), + 'css/header-footer': path.resolve( + __dirname, + './dev/scss', + 'header-footer.scss', + ), + 'css/editor-styles': path.resolve( + __dirname, + './dev/scss', + 'editor-styles.scss', + ), + 'css/editor': path.resolve(__dirname, './dev/scss', 'editor.scss'), + 'css/customizer': path.resolve(__dirname, './dev/scss', 'customizer.scss'), - 'js/hello-editor': path.resolve( __dirname, './dev/js/editor', 'hello-editor.js' ), - 'js/hello-frontend': path.resolve( __dirname, './dev/js/frontend', 'hello-frontend.js' ), - 'js/hello-home-app': path.resolve( modulesDir, 'admin-home/assets/js', 'hello-elementor-admin.js' ), - 'js/hello-elementor-menu': path.resolve( modulesDir, 'admin-home/assets/js', 'hello-elementor-menu.js' ), - 'js/hello-elementor-settings': path.resolve( modulesDir, 'admin-home/assets/js', 'hello-elementor-settings.js' ), - 'js/hello-elementor-topbar': path.resolve( modulesDir, 'admin-home/assets/js', 'hello-elementor-topbar.js' ), - 'js/hello-conversion-banner': path.resolve( modulesDir, 'admin-home/assets/js', 'hello-elementor-conversion-banner.js' ), + 'js/hello-editor': path.resolve( + __dirname, + './dev/js/editor', + 'hello-editor.js', + ), + 'js/hello-frontend': path.resolve( + __dirname, + './dev/js/frontend', + 'hello-frontend.js', + ), + 'js/hello-home-app': path.resolve( + modulesDir, + 'admin-home/assets/js', + 'hello-elementor-admin.js', + ), + 'js/hello-elementor-menu': path.resolve( + modulesDir, + 'admin-home/assets/js', + 'hello-elementor-menu.js', + ), + 'js/hello-elementor-settings': path.resolve( + modulesDir, + 'admin-home/assets/js', + 'hello-elementor-settings.js', + ), + 'js/hello-elementor-topbar': path.resolve( + modulesDir, + 'admin-home/assets/js', + 'hello-elementor-topbar.js', + ), + 'js/hello-conversion-banner': path.resolve( + modulesDir, + 'admin-home/assets/js', + 'hello-elementor-conversion-banner.js', + ), }; module.exports = { @@ -33,7 +69,7 @@ module.exports = { entry: entryPoints, output: { ...defaultConfig.output, - path: path.resolve( __dirname, './assets' ), + path: path.resolve(__dirname, './assets'), chunkFilename: 'js/[name].js', clean: false, }, @@ -41,23 +77,23 @@ module.exports = { // Include WP's plugin config. ...defaultConfig.plugins, - new CopyWebpackPlugin( { + new CopyWebpackPlugin({ patterns: [ { - from: path.resolve( modulesDir, 'admin-home/assets/images' ), + from: path.resolve(modulesDir, 'admin-home/assets/images'), to: imagesPath, }, { - from: path.resolve( __dirname, 'dev/images' ), + from: path.resolve(__dirname, 'dev/images'), to: imagesPath, }, ], - } ), + }), // Removes the empty `.js` files generated by webpack but // sets it after WP has generated its `*.asset.php` file. - new RemoveEmptyScriptsPlugin( { + new RemoveEmptyScriptsPlugin({ stage: RemoveEmptyScriptsPlugin.STAGE_AFTER_PROCESS_PLUGINS, - } ), + }), ], }, };