diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index ed1f8058..f159e3be 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -18,7 +18,7 @@ jobs: with: build: npm run build --workspaces --if-present start: npm run start-test-env - wait-on: http://localhost:8888 + wait-on: 'http://localhost:8888/wp-admin/index.php, http://localhost:8889' wait-on-timeout: 220 browser: chrome record: true diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 921316b9..d02ab05e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -59,7 +59,7 @@ Whenever a PR gets merged into the `develop` branch, a GitHub Action runs that w ### Deploying `patch`, `minor`, or `major` versions -Whenever a new `patch`, `minor`, or `major` version of the package should get released, the code first needs to get merged into the `trunk` branch. Once the code is on the `trunk` brach you need to head to the `Actions` tab in the repository and select the [`Publish new version to NPM`](https://github.com/10up/block-components/actions/workflows/release-npm-version.yml) Action in the sidebar. +Whenever a new `patch`, `minor`, or `major` version of the package should get released, the code first needs to get merged into the `trunk` branch. Once the code is on the `trunk` branch you need to head to the `Actions` tab in the repository and select the [`Publish new version to NPM`](https://github.com/10up/block-components/actions/workflows/release-npm-version.yml) Action in the sidebar. ![GitHub Actions Window](./images/release-action-page.png) diff --git a/components/is-admin/index.tsx b/components/is-admin/index.tsx index a44dbd63..14642ee5 100644 --- a/components/is-admin/index.tsx +++ b/components/is-admin/index.tsx @@ -16,7 +16,7 @@ interface IsAdminProps { /* * IsAdmin * - * A wrapper component that checks wether the current user has admin capabilities + * A wrapper component that checks whether the current user has admin capabilities * and only returns the child components if the user is an admin. You can pass a * fallback component via the fallback prop. */ diff --git a/components/media-toolbar/index.tsx b/components/media-toolbar/index.tsx index 12adb23e..b4e88ce9 100644 --- a/components/media-toolbar/index.tsx +++ b/components/media-toolbar/index.tsx @@ -17,7 +17,7 @@ interface MediaToolbarProps { onRemove: (event: React.MouseEvent) => void; /** - * Wether or not the Remove Image button should be shown. + * Whether or not the Remove Image button should be shown. */ isOptional?: boolean; diff --git a/components/rich-text-character-limit/readme.md b/components/rich-text-character-limit/readme.md index b3a2b1df..057edd81 100644 --- a/components/rich-text-character-limit/readme.md +++ b/components/rich-text-character-limit/readme.md @@ -8,7 +8,7 @@ https://user-images.githubusercontent.com/20684594/195820053-dcc05c91-b5bc-45db- -`RichTextCharacterLimit` extends `RichText` and can accept all the same props as `RichText` does. Please reffer to the [official RichText documentation](https://developer.wordpress.org/block-editor/reference-guides/richtext/). +`RichTextCharacterLimit` extends `RichText` and can accept all the same props as `RichText` does. Please refer to the [official RichText documentation](https://developer.wordpress.org/block-editor/reference-guides/richtext/). ## Usage diff --git a/cypress/e2e/ColorSettings.spec.js b/cypress/e2e/ColorSettings.spec.js new file mode 100644 index 00000000..ba62ca89 --- /dev/null +++ b/cypress/e2e/ColorSettings.spec.js @@ -0,0 +1,55 @@ +/// + +context('ColorSettings', () => { + + beforeEach(() => { + cy.loginToWordPress(); + cy.createPost({title: 'Color Settings Component Example'}); + cy.insertBlock('Color Settings Component Example'); + }); + + it('Allows the user to pick the block and displays it', () => { + cy.get('.wp-block-example-color-settings-example').should('be.visible'); + + cy.savePost(); + }) + + it('Allows the user to use a custom color and displays it', () => { + cy.get('.components-color-palette__custom-color-button').first().click(); + cy.get('.components-input-control__input').focus().clear().type('ff9400'); + cy.get('.components-color-palette__custom-color-name').first().should('have.text', 'Custom'); + cy.get('.components-color-palette__custom-color-value').first().should('have.text', '#ff9400'); + }) + + it('Allows the user to clear the color', () => { + cy.get('.components-color-palette__custom-color-button').first().click(); + cy.get('.components-input-control__input').focus().clear().type('ff9400'); + cy.get('.components-color-palette__custom-color-name').first().should('have.text', 'Custom'); + cy.get('.components-color-palette__custom-color-value').first().should('have.text', '#ff9400'); + + cy.get('.components-circular-option-picker__clear').first().click(); + cy.get('.components-color-palette__custom-color-name').first().should('have.text', 'No color selected'); + cy.get('.components-color-palette__custom-color-value').first().should('have.text', ''); + }) + + it('Allows the user to add custom label and help text', () => { + cy.get('.wp-block-example-color-settings-example').should('be.visible'); + + cy.get('.wp-block-example-color-settings-example .components-base-control__label').first().should('have.text', 'Color Setting - Label'); + cy.get('.wp-block-example-color-settings-example .components-base-control__help').first().should('have.text', 'Color Setting - Help Text'); + }) + + it('Allows the user to pick from predefined colors', () => { + cy.get('.components-circular-option-picker__option').first().click(); + cy.get('.components-color-palette__custom-color-name').first().should('have.text', 'red'); + cy.get('.components-color-palette__custom-color-value').first().should('have.text', '#f00'); + + cy.get('.components-circular-option-picker__option').eq(1).click(); + cy.get('.components-color-palette__custom-color-name').first().should('have.text', 'white'); + cy.get('.components-color-palette__custom-color-value').first().should('have.text', '#fff'); + + cy.get('.components-circular-option-picker__option').eq(2).click(); + cy.get('.components-color-palette__custom-color-name').first().should('have.text', 'blue'); + cy.get('.components-color-palette__custom-color-value').first().should('have.text', '#00f'); + }); +}) diff --git a/cypress/e2e/Counter.spec.js b/cypress/e2e/Counter.spec.js new file mode 100644 index 00000000..ca84b9db --- /dev/null +++ b/cypress/e2e/Counter.spec.js @@ -0,0 +1,56 @@ +/// + +context('Counter', () => { + + beforeEach(() => { + cy.loginToWordPress(); + cy.createPost({title: 'Counter Example'}); + cy.insertBlock('Counter Example'); + }); + + it('Allows the user to pick the block and displays it', () => { + cy.get('.wp-block-example-counter').should('exist'); + cy.get('.wp-block-example-counter').should('be.visible'); + + cy.savePost(); + }) + + it('Has the counter component in the block', () => { + cy.get('.wp-block-example-counter').should('exist'); + cy.get('.wp-block-example-counter').should('be.visible'); + + cy.get('.wp-block-example-counter .tenup--block-components__character-count').should('exist'); + }) + + it('Has the correct starting value', () => { + cy.get('.wp-block-example-counter .tenup--block-components__character-count__count').should('exist'); + cy.get('.wp-block-example-counter .tenup--block-components__character-count__count').should('have.text', '0'); + + cy.get('.wp-block-example-counter .tenup--block-components__character-count__limit').should('exist'); + cy.get('.wp-block-example-counter .tenup--block-components__character-count__limit').should('have.text', '20'); + }) + + it('Updates the count appropriately', () => { + cy.get('.wp-block-example-counter .components-text-control__input').focus().clear().type('0123456789'); + + cy.get('.wp-block-example-counter .tenup--block-components__character-count__count').should('exist'); + cy.get('.wp-block-example-counter .tenup--block-components__character-count__count').should('have.text', '10'); + + cy.get('.wp-block-example-counter .tenup--block-components__character-count__limit').should('exist'); + cy.get('.wp-block-example-counter .tenup--block-components__character-count__limit').should('have.text', '20'); + }) + + it('Updates to the approaching limit state appropriately', () => { + + cy.get('.wp-block-example-counter .components-text-control__input').focus().clear().type('01234567891234567'); + + cy.get('.wp-block-example-counter .tenup--block-components__circular-progress').should('have.class', 'is-approaching-limit'); + }) + + it('Updates to the is over limit state appropriately', () => { + + cy.get('.wp-block-example-counter .components-text-control__input').focus().clear().type('01234567891234567890'); + + cy.get('.wp-block-example-counter .tenup--block-components__circular-progress').should('have.class', 'is-over-limit'); + }) +}) \ No newline at end of file diff --git a/cypress/e2e/PostFeaturedImage.spec.js b/cypress/e2e/PostFeaturedImage.spec.js new file mode 100644 index 00000000..e635c334 --- /dev/null +++ b/cypress/e2e/PostFeaturedImage.spec.js @@ -0,0 +1,71 @@ +/// + +context('PostFeaturedImage', () => { + + beforeEach(() => { + cy.loginToWordPress(); + cy.createPost({title: 'Custom Post Featured Image'}); + cy.insertBlock('Custom Post Featured Image'); + }); + + it('Allows the user to pick the block and displays it', () => { + cy.get('.wp-block-example-custom-post-featured-image').should('exist'); + cy.get('.wp-block-example-custom-post-featured-image').should('be.visible'); + + cy.savePost(); + }) + + it('Allows the user to pick an image from the media library and displays it inline', () => { + cy.get('.wp-block-example-custom-post-featured-image button').contains('Media Library').click(); + cy.get('#menu-item-browse').click(); + cy.get('.attachment-preview').first().click(); + cy.get('#attachment-details-alt-text').type('Test Alt Text'); + cy.get('.media-button-select').contains('Select').click(); + + cy.get('.wp-block-example-custom-post-featured-image img').scrollIntoView({offset: -50}) + cy.get('.wp-block-example-custom-post-featured-image img').should('be.visible'); + + cy.get('.wp-block-example-custom-post-featured-image img') + .should('have.attr', 'alt'); + + cy.get('.wp-block-example-custom-post-featured-image img') + .should('have.attr', 'src'); + }) + + it('Syncs with the post featured image', () => { + cy.get('.wp-block-example-custom-post-featured-image button').contains('Media Library').click(); + cy.get('#menu-item-browse').click(); + cy.get('.attachment-preview').first().click(); + cy.get('#attachment-details-alt-text').type('Test Alt Text'); + cy.get('.media-button-select').contains('Select').click(); + + cy.get('.wp-block-example-custom-post-featured-image img').scrollIntoView({offset: -50}) + cy.get('.wp-block-example-custom-post-featured-image img').should('be.visible'); + + cy.get('[data-tab-id="edit-post/document"]').click(); + + cy.get('.components-button.components-panel__body-toggle').contains('Featured image').then($button => { + if ($button.attr('aria-expanded') === 'false') { + cy.get('.components-button.components-panel__body-toggle').contains('Featured image').click(); + } + }) + + cy.get('.editor-post-featured-image img').should('exist').then(($a) => { + const src1 = $a.attr('src'); + const cleanedSrc1 = src1.replace(/-\d+x\d+(?=\.\w+$)/, ''); + + cy.get('.wp-block-example-custom-post-featured-image__image').should('exist'); + cy.get('.wp-block-example-custom-post-featured-image__image').should('have.attr', 'src'); + cy.get('.wp-block-example-custom-post-featured-image__image').then(($b) => { + console.log($b); + const src2 = $b.attr('src'); + const cleanedSrc2 = src2.replace(/-\d+x\d+(?=\.\w+$)/, ''); + + expect(cleanedSrc1).to.equal(cleanedSrc2); + }); + }); + + cy.get('.components-button.editor-post-featured-image__action').contains('Remove').click(); + cy.get('.wp-block-example-custom-post-featured-image__image').should('not.exist'); + }) +}) diff --git a/cypress/e2e/PostMeta.spec.js b/cypress/e2e/PostMeta.spec.js new file mode 100644 index 00000000..99595742 --- /dev/null +++ b/cypress/e2e/PostMeta.spec.js @@ -0,0 +1,125 @@ +/// + +context('Post Meta', () => { + + beforeEach(() => { + cy.loginToWordPress(); + cy.createPost({title: 'Post Meta', postType: 'book' }); + cy.insertBlock('Post Meta'); + }); + + it('Allows the user to pick the block and displays it', () => { + cy.get('.wp-block-example-post-meta').should('exist'); + cy.get('.wp-block-example-post-meta').should('be.visible'); + + cy.savePost(); + }) + + it('Allows the user to pick a variation', () => { + + cy.get('.wp-block-example-post-meta .block-editor-block-variation-picker__variation').its('length').should('be.gt', 0); + cy.get('.wp-block-example-post-meta .block-editor-block-variation-picker__variation').first().click(); + }) + + it('Allows text to be entered into the post meta field', () => { + // Author + cy.get('.wp-block-example-post-meta .block-editor-block-variation-picker__variation').first().click(); + + cy.get('.wp-block-example-post-meta .block-editor-rich-text__editable[metakey="author"]').focus().clear().type('This is a test'); + cy.get('.wp-block-example-post-meta .block-editor-rich-text__editable[metakey="author"]').should('have.text', 'This is a test'); + }) + + it('Allows a number to be entered into the post meta field', () => { + // Price + cy.get('.wp-block-example-post-meta .block-editor-block-variation-picker__variation').eq(2).click(); + + cy.get('.wp-block-example-post-meta .components-input-control__input').focus().clear().type('10'); + cy.get('.wp-block-example-post-meta .components-input-control__input').should('have.value', '10'); + }) + + it('Allows a boolean to change the post meta field', () => { + // Is featured + cy.get('.wp-block-example-post-meta .block-editor-block-variation-picker__variation').eq(3).click(); + + cy.get('.wp-block-example-post-meta .components-form-toggle__input').check(); + cy.get('.wp-block-example-post-meta .components-form-toggle__input').should('be.checked'); + cy.get('.wp-block-example-post-meta .components-form-toggle').should('have.class', 'is-checked'); + }) + + it('Saves the data into post meta', () => { + // Author + cy.get('.wp-block-example-post-meta .block-editor-block-variation-picker__variation').first().click(); + cy.get('.wp-block-example-post-meta .block-editor-rich-text__editable[metakey="author"]').focus().clear().type('This is a test'); + + // Reset + cy.get('.block-editor-block-breadcrumb__button').contains('Book').click(); + + // Price + cy.insertBlock('Post Meta'); + cy.get('.wp-block-example-post-meta .block-editor-block-variation-picker__variation').eq(2).click(); + cy.get('.wp-block-example-post-meta .components-input-control__input').focus().clear().type('10'); + + // Reset + cy.get('.block-editor-block-breadcrumb__button').contains('Book').click(); + + // Is featured + cy.insertBlock('Post Meta'); + cy.get('.wp-block-example-post-meta .block-editor-block-variation-picker__variation').eq(3).click(); + cy.get('.wp-block-example-post-meta .components-form-toggle__input').check(); + + // Reset + cy.get('.block-editor-block-breadcrumb__button').contains('Book').click(); + + // Intercept the network request related to publishing the post + cy.intercept({ + method: 'POST', + url: '/index.php?rest_route=%2Fwp%2Fv2%2Fbooks%2F*', + }).as('publishPost'); + + cy.savePost(); + + // Wait for the intercepted request to complete + cy.wait('@publishPost'); + + cy.reload(); + + cy.get('.components-dropdown-menu__toggle[aria-label="Options"]').click(); + cy.get('.components-menu-item__button').contains('Preferences').click(); + cy.get('.components-toggle-control__label').contains('Custom fields').then(($label) => { + const attrFor = $label.attr('for'); + + cy.get(`#${attrFor}`).then($input => { + if (!$input.is(':checked')) { + cy.get(`#${attrFor}`).check(); + cy.get('.edit-post-preferences-modal__custom-fields-confirmation-button').contains('Show & Reload Page').click(); + } else { + cy.get('.components-button[aria-label="Close"]').click(); + } + }) + + cy.get('button').contains('Toggle panel: Custom Fields').then($button => { + if ($button.attr('aria-expanded') === 'false') { + cy.get('button').contains('Toggle panel: Custom Fields').click(); + } + }) + + cy.get('input[value="author"]').should('exist').then($input => { + const id = $input.attr('id'); + + cy.get(`#${id.replace('key','value')}`).should('have.value', 'This is a test'); + }) + + cy.get('input[value="is_featured"]').should('exist').then($input => { + const id = $input.attr('id'); + + cy.get(`#${id.replace('key','value')}`).should('have.value', '1'); + }) + + cy.get('input[value="price"]').should('exist').then($input => { + const id = $input.attr('id'); + + cy.get(`#${id.replace('key','value')}`).should('have.value', '10'); + }) + }) + }) +}) diff --git a/cypress/e2e/PostTitle.spec.js b/cypress/e2e/PostTitle.spec.js new file mode 100644 index 00000000..06c3c975 --- /dev/null +++ b/cypress/e2e/PostTitle.spec.js @@ -0,0 +1,29 @@ +/// + +context('PostTitle', () => { + + beforeEach(() => { + cy.loginToWordPress(); + cy.createPost({title: 'Custom Post Title'}); + cy.insertBlock('Custom Post Title'); + }); + + it('Allows the user to pick the block and displays it', () => { + cy.get('.wp-block-example-custom-post-title').should('exist'); + cy.get('.wp-block-example-custom-post-title').should('be.visible'); + + cy.savePost(); + }) + + it('Allows text to be entered', () => { + cy.get('.wp-block-example-custom-post-title .block-editor-rich-text__editable').focus().clear().type('This is a test'); + cy.get('.wp-block-example-custom-post-title .block-editor-rich-text__editable').should('have.text', 'This is a test'); + }) + + it('Should sync the text entered in the block with the post title', () => { + cy.get('.wp-block-example-custom-post-title .block-editor-rich-text__editable').focus().clear().type('This is a test'); + cy.get('.wp-block-example-custom-post-title .block-editor-rich-text__editable').should('have.text', 'This is a test'); + + cy.get('.wp-block-post-title').should('have.text', 'This is a test'); + }) +}) diff --git a/cypress/e2e/RichTextCharacterLimit.spec.js b/cypress/e2e/RichTextCharacterLimit.spec.js new file mode 100644 index 00000000..23089aa0 --- /dev/null +++ b/cypress/e2e/RichTextCharacterLimit.spec.js @@ -0,0 +1,63 @@ +/// + +context('RichTextCharacterLimit', () => { + + beforeEach(() => { + cy.loginToWordPress(); + cy.createPost({title: 'Rich Text Character Limit'}); + cy.insertBlock('Rich Text Character Limit'); + }); + + it('Allows the user to pick the block and displays it', () => { + cy.get('.wp-block-example-rich-text-character-limit').should('exist'); + cy.get('.wp-block-example-rich-text-character-limit').should('be.visible'); + + cy.savePost(); + }) + + it('Allows text to be entered', () => { + cy.get('.wp-block-example-rich-text-character-limit .block-editor-rich-text__editable').focus().clear().type('This is a test'); + cy.get('.wp-block-example-rich-text-character-limit .block-editor-rich-text__editable').should('have.text', 'This is a test'); + }) + + it('Has the correct starting value', () => { + cy.get('.wp-block-example-rich-text-character-limit .tenup--block-components__character-count__count').should('exist'); + cy.get('.wp-block-example-rich-text-character-limit .tenup--block-components__character-count__count').should('have.text', '0'); + + cy.get('.wp-block-example-rich-text-character-limit .tenup--block-components__character-count__limit').should('exist'); + cy.get('.wp-block-example-rich-text-character-limit .tenup--block-components__character-count__limit').should('have.text', '30'); + }) + + it('Updates the count appropriately', () => { + cy.get('.wp-block-example-rich-text-character-limit .block-editor-rich-text__editable').focus().clear().type('This is a test'); + cy.get('.wp-block-example-rich-text-character-limit .tenup--block-components__character-count__count').should('have.text', '14'); + + cy.get('.wp-block-example-rich-text-character-limit .tenup--block-components__character-count__limit').should('exist'); + cy.get('.wp-block-example-rich-text-character-limit .tenup--block-components__character-count__limit').should('have.text', '30'); + }) + + it('Updates to the approaching limit state appropriately', () => { + + cy.get('.wp-block-example-rich-text-character-limit .block-editor-rich-text__editable').focus().clear().type('0123456789012345678901234'); + + cy.get('.wp-block-example-rich-text-character-limit .tenup--block-components__circular-progress').should('have.class', 'is-approaching-limit'); + }) + + it('Updates to the is over limit state appropriately', () => { + + cy.get('.wp-block-example-rich-text-character-limit .block-editor-rich-text__editable').focus().clear().type('012345678901234567890123456789'); + + cy.get('.wp-block-example-rich-text-character-limit .tenup--block-components__circular-progress').should('have.class', 'is-over-limit'); + }) + + it('Shows the toolbar controls appropriately', () => { + + cy.get('.wp-block-example-rich-text-character-limit .block-editor-rich-text__editable').focus() + + cy.get('.components-toolbar-button[aria-label="Bold"]').should('exist'); + cy.get('.components-toolbar-button[aria-label="Bold"]').should('be.visible'); + + cy.get('.components-toolbar-button[aria-label="Link"]').should('exist'); + cy.get('.components-toolbar-button[aria-label="Link"]').should('be.visible'); + }); +}) diff --git a/example/README.md b/example/README.md index b3704dc5..baf0d4c0 100644 --- a/example/README.md +++ b/example/README.md @@ -1,6 +1,6 @@ # Example -This Example is a simple WordPress Plugin that shows some of the block components in use in an actuall block. +This Example is a simple WordPress Plugin that shows some of the block components in use in an actual block. It is setup with @wordpress/env to that you can start a clean WordPress environment with this plugin by running `npm run wp-env start`. In order to build the blocks that are included in this plugin you need to run `npm run build` or if you want to watch for changes `npm run start`. The environment will be available under [http://localhost:8888](http://localhost:8888) and the credentials to login to the admin are: `admin` `password` \ No newline at end of file diff --git a/example/src/extensions/background-pattern/index.ts b/example/src/extensions/background-pattern/index.ts index bcf04a7a..e075a120 100644 --- a/example/src/extensions/background-pattern/index.ts +++ b/example/src/extensions/background-pattern/index.ts @@ -32,8 +32,8 @@ if ( ! hasBackgroundPattern ) { return; } -const backgroundPatternColorClassName = `has-${backgroundPatternColor}-background-patern-color`; -const backgroundPatternShapeClassName = `has-${backgroundPatternShape}-background-patern-shape`; +const backgroundPatternColorClassName = `has-${backgroundPatternColor}-background-pattern-color`; +const backgroundPatternShapeClassName = `has-${backgroundPatternShape}-background-pattern-shape`; return `has-background-pattern ${backgroundPatternColorClassName} ${backgroundPatternShapeClassName}`; diff --git a/hooks/use-primary-term/index.ts b/hooks/use-primary-term/index.ts index b2287066..c7b06f1a 100644 --- a/hooks/use-primary-term/index.ts +++ b/hooks/use-primary-term/index.ts @@ -1,7 +1,7 @@ import { useSelect } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; import { store as coreStore } from '@wordpress/core-data'; -import type { WP_REST_API_Taxonomy } from 'wp-types'; +import type { WP_REST_API_Term } from 'wp-types'; import { usePost } from '../use-post'; import { useIsPluginActive } from '../use-is-plugin-active'; import { useIsSupportedTaxonomy } from '../use-is-supported-taxonomy'; @@ -64,7 +64,7 @@ export const usePrimaryTerm = (taxonomyName: string) => { } const { getEntityRecord } = select(coreStore); - return getEntityRecord('taxonomy', taxonomyName, primaryTermId); + return getEntityRecord('taxonomy', taxonomyName, primaryTermId); }, [primaryTermId], ); diff --git a/package.json b/package.json index 9bc79ce8..c4e3478a 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,9 @@ "build-test-env": "npm run build --workspaces --if-present", "start-test-env": "npm run start-env -w example/ && npm run import-media -w example/", "test:e2e": "cypress open", - "prepublishOnly": "npm run build" + "prepublishOnly": "npm run build", + "cypress:open": "cypress open --config-file ./cypress.config.js --e2e --browser chrome", + "cypress:run": "cypress run --config-file ./cypress.config.js" }, "repository": { "type": "git",