From 095a417e93d78626c3902f85ed68c22ecf640ff1 Mon Sep 17 00:00:00 2001 From: ttahmouch Date: Tue, 26 Oct 2021 13:29:11 -0400 Subject: [PATCH 01/10] TT | 4058 | Resolving runtime error when running `npm run native test`. ``` console.error Warning: Each child in a list should have a unique "key" prop. Check the render method of `StylePreview`. See https://reactjs.org/link/warning-keys for more information. ... 86 | > 87 | > 88 | | ^ 89 | { isActive && 90 | getOutline( [ styles.outline, innerOutlineStyle ] ) } 91 | - outlineStyles.map( ( outlineStyle ) => { + outlineStyles.map( ( outlineStyle, index ) => { return ( ); } ); From c91bef9fbe71066b59e742238a0b56849c2097cf Mon Sep 17 00:00:00 2001 From: ttahmouch Date: Tue, 26 Oct 2021 14:36:35 -0400 Subject: [PATCH 02/10] TT | 4058 | Mocking `Image.getSize` and `Clipboard.[gs]etString` for the whole test runtime. This will help avoid inadvertently making async I/O operations to underlying Native Modules or HTTP when integration-testing our components to ensure more stability in the test runtime. + Mocked `Image.getSize` specifically, i.e., not the whole `Image` module from `react-native`. + Rewrote the implementation to simply call the provided success callback synchronously. + It returns default values of `0` for `width` and `height` pretending to have retrieved the image URI async using HTTP. + Mocked `Clipboard.[gs]etString` which is the entire `Clipboard` module from `react-native`. + **Note:** `Clipboard` was deprecated in favor of `@react-native-community/clipboard` and will be removed in the future. + Rewrote the implementation to simply do nothing on `setString`. + Rewrote the implementation to simply return a `Promise` that resolves to `''` on `getString`. + Staging strings returned from the `Clipboard` in future integration tests can be as simple as `Clipboard.getString.mockReturnValueOnce( 'value' )`. --- test/native/setup.js | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/test/native/setup.js b/test/native/setup.js index 318fbe03ff46ba..17b40c567aa873 100644 --- a/test/native/setup.js +++ b/test/native/setup.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { NativeModules as RNNativeModules } from 'react-native'; +import { Image, NativeModules as RNNativeModules } from 'react-native'; RNNativeModules.UIManager = RNNativeModules.UIManager || {}; RNNativeModules.UIManager.RCTView = RNNativeModules.UIManager.RCTView || {}; @@ -152,6 +152,17 @@ jest.mock( 'react-native-reanimated', () => { // https://reactnavigation.org/docs/testing/#mocking-native-modules jest.mock( 'react-native/Libraries/Animated/NativeAnimatedHelper' ); +/** + * Note: Clipboard has been extracted from react-native core and will be removed in a future release. + * It can now be installed and imported from @react-native-community/clipboard instead of 'react-native'. + * + * @see node_modules/react-native/Libraries/Components/Clipboard/Clipboard.js + */ +jest.mock( 'react-native/Libraries/Components/Clipboard/Clipboard', () => ( { + getString: jest.fn( () => new Promise( ( resolve ) => resolve( '' ) ) ), + setString: jest.fn(), +} ) ); + // We currently reference TextStateInput (a private module) within // react-native-aztec/src/AztecView. Doing so requires that we mock it via its // internal path to avoid "TypeError: Cannot read property 'Commands' of @@ -195,3 +206,7 @@ jest.mock( '@wordpress/compose', () => { ] ), }; } ); + +jest.spyOn( Image, 'getSize' ).mockImplementation( ( url, success ) => + success( 0, 0 ) +); From da94d682abe80d3b6092ac43cb58c34eba43ab8a Mon Sep 17 00:00:00 2001 From: ttahmouch Date: Tue, 26 Oct 2021 15:07:04 -0400 Subject: [PATCH 03/10] TT | 4058 | TDDing the removal of auto-population of URLs from the Clipboard into the `Link Settings > Link To` field when editing an `Image` block type. + Deliberately removed existing unit tests in this module for the moment in order to make transitioning from `react-test-renderer` to `@testing-library/react-native` easier to manage. They will either be re-added in the near future, or converted to complementary integration tests. + Deliberately structured the test cases in "Given-When-Then (GWT) Gherkin-style." + Added the first test that necessitates the change for forcing auto-population of URLs from the Clipboard to NOT take place when editing an `` block. + Intend to parameterize the tests to see how the same tests may be leveraged for other block types that have `Link Settings`, e.g., `Embed` and `Button` to avoid repeating the tests for those block types. + Integration tests will eventually be written for various planned user experience scenarios including: ``` GIVEN the Link Settings sheet displays, AND the Clipboard has a URL copied, THEN the `Link to` field in the Link Settings should be populated with the placeholder text, i.e., `Search or type URL`. GIVEN the Link Picker sheet displays, AND the Clipboard has a URL copied that is different from the contents of the text input field, THEN the `From Clipboard` table cell should be populated with the URL from the Clipboard. GIVEN the Link Picker sheet displays with the `From Clipboard` table cell, WHEN the Clipboard is cleared or changed to something that is NOT a URL, THEN the `From Clipboard` table cell should disappear. GIVEN the Link Picker sheet displays, AND the contents of the Clipboard are IDENTICAL to the contents of the text input field, THEN do NOT display the `From Clipboard` table cell. GIVEN the Link Picker sheet displays, AND the `From Clipboard` table cell is pressed, THEN the `Search or type URL` text input field is populated with the URL from the Clipboard, AND the `Add this link` table cell is repopulated with the new URL from the Clipboard. ``` --- .../block-library/src/image/edit.native.js | 2 +- .../src/image/test/edit.native.js | 144 ++++++++++++------ 2 files changed, 97 insertions(+), 49 deletions(-) diff --git a/packages/block-library/src/image/edit.native.js b/packages/block-library/src/image/edit.native.js index a23564c3945182..6d0e6cbdb8ce16 100644 --- a/packages/block-library/src/image/edit.native.js +++ b/packages/block-library/src/image/edit.native.js @@ -114,7 +114,7 @@ export class ImageEdit extends Component { label: __( 'Image Link URL' ), placeholder: __( 'Add URL' ), autoFocus: false, - autoFill: true, + autoFill: false, }, openInNewTab: { label: __( 'Open in new tab' ), diff --git a/packages/block-library/src/image/test/edit.native.js b/packages/block-library/src/image/test/edit.native.js index 86b4751177da22..6c34813278de8d 100644 --- a/packages/block-library/src/image/test/edit.native.js +++ b/packages/block-library/src/image/test/edit.native.js @@ -1,65 +1,113 @@ /** * External dependencies */ -import renderer from 'react-test-renderer'; +import { Clipboard } from 'react-native'; +import { fireEvent, initializeEditor, waitFor } from 'test/helpers'; +/** + * WordPress dependencies + */ +import { registerCoreBlocks } from '@wordpress/block-library'; +import { getBlockTypes, unregisterBlockType } from '@wordpress/blocks'; /** - * Internal dependencies + * Utility function to unregister all core block types previously registered + * when staging the Redux Store `beforeAll` integration tests start running. + * + * This should probably be extracted into a utility module for future tests. */ -import { ImageEdit } from '../edit'; -import { NEW_TAB_REL } from '../constants'; +const unregisterBlocks = () => { + const blocks = getBlockTypes(); -const getStylesFromColorScheme = () => { - return { color: 'white' }; + blocks.forEach( ( { name } ) => unregisterBlockType( name ) ); }; -const setAttributes = jest.fn(); - -const getImageComponent = ( attributes = {} ) => ( - -); - -describe( 'Image Block', () => { - beforeEach( () => { - setAttributes.mockReset(); +/** + * + Deliberately removed existing unit tests in this module for the moment + * in order to make transitioning from `react-test-renderer` to + * `@testing-library/react-native` easier to manage. They will either be + * re-added in the near future, or converted to complementary integration tests. + * + * + Deliberately structured the test cases in "Given-When-Then (GWT) Gherkin-style." + * + * + Added the first test that necessitates the change for forcing auto-population + * of URLs from the Clipboard to NOT take place when editing an `` block. + * + * + Intend to parameterize the tests to see how the same tests may be leveraged + * for other block types that have `Link Settings`, e.g., `Embed` and `Button` + * to avoid repeating the tests for those block types. + * + * + Integration tests will eventually be written for various planned user + * experience scenarios including: + * + * ``` + * GIVEN the Link Settings sheet displays, + * AND the Clipboard has a URL copied, + * THEN the `Link to` field in the Link Settings should be populated with the placeholder text, i.e., `Search or type URL`. + * + * GIVEN the Link Picker sheet displays, + * AND the Clipboard has a URL copied that is different from the contents of the text input field, + * THEN the `From Clipboard` table cell should be populated with the URL from the Clipboard. + * + * GIVEN the Link Picker sheet displays with the `From Clipboard` table cell, + * WHEN the Clipboard is cleared or changed to something that is NOT a URL, + * THEN the `From Clipboard` table cell should disappear. + * + * GIVEN the Link Picker sheet displays, + * AND the contents of the Clipboard are IDENTICAL to the contents of the text input field, + * THEN do NOT display the `From Clipboard` table cell. + * + * GIVEN the Link Picker sheet displays, + * AND the `From Clipboard` table cell is pressed, + * THEN the `Search or type URL` text input field is populated with the URL from the Clipboard, + * AND the `Add this link` table cell is repopulated with the new URL from the Clipboard. + * ``` + */ +describe( '', () => { + beforeAll( () => { + registerCoreBlocks(); } ); - it( 'renders without crashing', () => { - const component = renderer.create( getImageComponent() ); - const rendered = component.toJSON(); - expect( rendered ).toBeTruthy(); + afterAll( () => { + unregisterBlocks(); } ); - it( 'sets link target', () => { - const component = renderer.create( getImageComponent() ); - const instance = component.getInstance(); - - instance.onSetNewTab( true ); - - expect( setAttributes ).toHaveBeenCalledWith( { - linkTarget: '_blank', - rel: undefined, + /** + * GIVEN an EDITOR is displayed with an IMAGE BLOCK; + * GIVEN the CLIPBOARD has a URL copied; + * WHEN the USER selects the EDIT BUTTON on the IMAGE BLOCK; + */ + // eslint-disable-next-line jest/no-done-callback + it( 'should display the LINK SETTINGS with an EMPTY LINK TO field.', async ( done ) => { + // Arrange + const url = 'https://tonytahmouchtest.files.wordpress.com'; + const { + getByA11yHint, + getByA11yLabel, + getByText, + } = await initializeEditor( { + initialHtml: ` + +
+ +
+ + `, } ); - } ); - - it( 'unset link target', () => { - const component = renderer.create( - getImageComponent( { - linkTarget: '_blank', - rel: NEW_TAB_REL.join( ' ' ), - } ) + Clipboard.getString.mockReturnValueOnce( url ); + // Act + fireEvent.press( + await waitFor( () => + getByA11yHint( 'Double tap and hold to edit' ) + ) ); - const instance = component.getInstance(); - - instance.onSetNewTab( false ); - - expect( setAttributes ).toHaveBeenCalledWith( { - linkTarget: undefined, - rel: undefined, - } ); + fireEvent.press( + await waitFor( () => getByA11yLabel( 'Open Settings' ) ) + ); + // Assert + const expectation = + 'The URL from the Clipboard should NOT be displayed in the Link Settings > Link To field.'; + waitFor( () => getByText( url ), { timeout: 50, interval: 10 } ) + .then( () => done.fail( expectation ) ) + .catch( () => done() ); } ); } ); From 6877894956de99853883096f34709c999d36d433 Mon Sep 17 00:00:00 2001 From: ttahmouch Date: Fri, 29 Oct 2021 10:59:21 -0400 Subject: [PATCH 04/10] TT | 4058 | Generalized the existing test assertion from the `` block to the `` block. + Added a utility function called `interactionWithUIElement` to interact with an element under test before assertions. This allows us to pretend that the user is interacting with the UI of the `` as if they are using the mobile app in order to stage a test. Right now it's fairly primitive, and is mostly used for `press` events. It can also be used for `layout` events at the moment, but it would need to be elaborated further to support events like `changeText` or `scroll`. + Used `interactionWithUIElement` to generalize a test to assert the same expectations regardless of whether the UX is displaying an `` block or `` block. The expected UX the test is asserting should be identical regardless of which block is being used in the ``. The only technical difference is how we'd find the elements in the view tree since they have different accessibility props to identify them, e.g., `getByA11yLabel` for the edit button block, and `getByA11yHint` for the edit image block. --- .../block-library/src/button/edit.native.js | 2 +- .../src/image/test/edit.native.js | 110 ++++++++++++++---- test/native/setup.js | 2 +- 3 files changed, 88 insertions(+), 26 deletions(-) diff --git a/packages/block-library/src/button/edit.native.js b/packages/block-library/src/button/edit.native.js index 3f4f301bd1b8d4..71066f3d481508 100644 --- a/packages/block-library/src/button/edit.native.js +++ b/packages/block-library/src/button/edit.native.js @@ -171,7 +171,7 @@ function ButtonEdit( props ) { label: __( 'Button Link URL' ), placeholder: __( 'Add URL' ), autoFocus: true, - autoFill: true, + autoFill: false, }, openInNewTab: { label: __( 'Open in new tab' ), diff --git a/packages/block-library/src/image/test/edit.native.js b/packages/block-library/src/image/test/edit.native.js index 6c34813278de8d..f96865e71c5943 100644 --- a/packages/block-library/src/image/test/edit.native.js +++ b/packages/block-library/src/image/test/edit.native.js @@ -9,6 +9,10 @@ import { fireEvent, initializeEditor, waitFor } from 'test/helpers'; import { registerCoreBlocks } from '@wordpress/block-library'; import { getBlockTypes, unregisterBlockType } from '@wordpress/blocks'; +/** + * TODO: Move this module to `` since it's not specific to the block type? + */ + /** * Utility function to unregister all core block types previously registered * when staging the Redux Store `beforeAll` integration tests start running. @@ -21,6 +25,32 @@ const unregisterBlocks = () => { blocks.forEach( ( { name } ) => unregisterBlockType( name ) ); }; +/** + * Utility function to interact with the element under test to pretend that + * the user pressed elements. This could also be possibly used for `onLayout` + * events as well, but currently I just wrote it for redundant `onPress` events. + * If another parameter is added, I don't see why it couldn't be used to provide + * text as user input into text fields. + * + * This should probably be extracted into a utility module for future tests. + * + * @param {Object[]} steps - Interaction steps to apply to the UI element under test. + * @param {Object} element - The UI element under test. + * @see node_modules/@testing-library/react-native/typings/index.d.ts + * @return {Promise} - The utility `await`s promises from `waitFor` so it's `async`. + */ +const interactionWithUIElement = async ( steps, element ) => { + for ( const step of steps ) { + const { find = {}, then = {} } = step; + const { event = '' } = then; + const [ query = '' ] = + Object.keys( find ).filter( ( key ) => !! find[ key ] ) || []; + const selector = find[ query ] || ''; + const subject = await waitFor( () => element[ query ]( selector ) ); + fireEvent[ event ]( subject ); + } +}; + /** * + Deliberately removed existing unit tests in this module for the moment * in order to make transitioning from `react-test-renderer` to @@ -62,7 +92,34 @@ const unregisterBlocks = () => { * AND the `Add this link` table cell is repopulated with the new URL from the Clipboard. * ``` */ -describe( '', () => { +describe.each( [ + [ + { + type: 'core/button', + initialHtml: ` + +
+ Link +
+ + `, + toJSON: () => 'core/button', + }, + ], + [ + { + type: 'core/image', + initialHtml: ` + +
+ +
+ + `, + toJSON: () => 'core/image', + }, + ], +] )( ' from %j', ( { type, initialHtml } ) => { beforeAll( () => { registerCoreBlocks(); } ); @@ -72,41 +129,46 @@ describe( '', () => { } ); /** - * GIVEN an EDITOR is displayed with an IMAGE BLOCK; + * GIVEN an EDITOR is displayed with an EDIT IMAGE BLOCK or EDIT BUTTON BLOCK; * GIVEN the CLIPBOARD has a URL copied; - * WHEN the USER selects the EDIT BUTTON on the IMAGE BLOCK; + * WHEN the USER selects the SETTINGS BUTTON on the EDIT IMAGE BLOCK or EDIT BUTTON BLOCK; */ // eslint-disable-next-line jest/no-done-callback it( 'should display the LINK SETTINGS with an EMPTY LINK TO field.', async ( done ) => { // Arrange const url = 'https://tonytahmouchtest.files.wordpress.com'; - const { - getByA11yHint, - getByA11yLabel, - getByText, - } = await initializeEditor( { - initialHtml: ` - -
- -
- - `, - } ); + const subject = await initializeEditor( { initialHtml } ); Clipboard.getString.mockReturnValueOnce( url ); + // Act - fireEvent.press( - await waitFor( () => - getByA11yHint( 'Double tap and hold to edit' ) - ) - ); - fireEvent.press( - await waitFor( () => getByA11yLabel( 'Open Settings' ) ) + const buttonBlockSelector = + type === 'core/button' ? 'Button Block. Row 1' : undefined; + const imageBlockSelector = + type === 'core/image' ? 'Double tap and hold to edit' : undefined; + await interactionWithUIElement( + [ + { + find: { + getByA11yLabel: buttonBlockSelector, + getByA11yHint: imageBlockSelector, + }, + then: { event: 'press' }, + }, + { + find: { getByA11yLabel: 'Open Settings' }, + then: { event: 'press' }, + }, + ], + subject ); + // Assert const expectation = 'The URL from the Clipboard should NOT be displayed in the Link Settings > Link To field.'; - waitFor( () => getByText( url ), { timeout: 50, interval: 10 } ) + waitFor( () => subject.getByText( url ), { + timeout: 50, + interval: 10, + } ) .then( () => done.fail( expectation ) ) .catch( () => done() ); } ); diff --git a/test/native/setup.js b/test/native/setup.js index 17b40c567aa873..3c3e664dc4d188 100644 --- a/test/native/setup.js +++ b/test/native/setup.js @@ -159,7 +159,7 @@ jest.mock( 'react-native/Libraries/Animated/NativeAnimatedHelper' ); * @see node_modules/react-native/Libraries/Components/Clipboard/Clipboard.js */ jest.mock( 'react-native/Libraries/Components/Clipboard/Clipboard', () => ( { - getString: jest.fn( () => new Promise( ( resolve ) => resolve( '' ) ) ), + getString: jest.fn( () => Promise.resolve( '' ) ), setString: jest.fn(), } ) ); From 95a0598877c124329811cbcd6c15ff5a306b6f28 Mon Sep 17 00:00:00 2001 From: ttahmouch Date: Wed, 3 Nov 2021 15:12:41 -0400 Subject: [PATCH 05/10] TT | 4058 | Tested scenarios where the `From Clipboard` button in the Link Picker **SHOULD/NOT** display to the user. + Tested some naive scenarios where the `From Clipboard` button **WOULD** or **WOULD NOT** display given the `Clipboard` has a **VALID** or **INVALID** URL copied. It is naive because it doesn't account for if the user has already copied the URL suggestion from the `Clipboard` into their Link Settings yet. Presumably, we **SHOULD NOT** suggest the URL if it is already the URL in their Link Settings (which will be a future test assertion). + Added a [Clipboard SVG](https://www.iconfinder.com/icons/211649/clipboard_icon) as a placeholder asset for the `From Clipboard` button until a final one is decided. It is MIT licensed. + Reimplemented `interactionWithUIElement` to **NOT** be an `async` function to avoid unnecessary warnings, i.e., `Warning: An update to ForwardRef(NavigationContainer) inside a test was not wrapped in act(...).`. --- .../src/image/test/edit.native.js | 183 ++++++++++++++++-- .../link-suggestion-item-cell.native.js | 8 +- .../src/mobile/link-picker/index.native.js | 48 ++++- packages/icons/src/index.js | 1 + packages/icons/src/library/clipboard.js | 20 ++ 5 files changed, 240 insertions(+), 20 deletions(-) create mode 100644 packages/icons/src/library/clipboard.js diff --git a/packages/block-library/src/image/test/edit.native.js b/packages/block-library/src/image/test/edit.native.js index f96865e71c5943..dda47b224cde45 100644 --- a/packages/block-library/src/image/test/edit.native.js +++ b/packages/block-library/src/image/test/edit.native.js @@ -8,6 +8,7 @@ import { fireEvent, initializeEditor, waitFor } from 'test/helpers'; */ import { registerCoreBlocks } from '@wordpress/block-library'; import { getBlockTypes, unregisterBlockType } from '@wordpress/blocks'; +import { __ } from '@wordpress/i18n'; /** * TODO: Move this module to `` since it's not specific to the block type? @@ -26,6 +27,12 @@ const unregisterBlocks = () => { }; /** + * This used to be an `async` function instead of an ordinary function that returns a Promise. + * It was deliberately changed because otherwise we'd get unnecessary error logs in the console that look like this: + * `Warning: An update to ForwardRef(NavigationContainer) inside a test was not wrapped in act(...).` + * + * @see https://github.com/WordPress/gutenberg/issues/35685 + * * Utility function to interact with the element under test to pretend that * the user pressed elements. This could also be possibly used for `onLayout` * events as well, but currently I just wrote it for redundant `onPress` events. @@ -37,18 +44,45 @@ const unregisterBlocks = () => { * @param {Object[]} steps - Interaction steps to apply to the UI element under test. * @param {Object} element - The UI element under test. * @see node_modules/@testing-library/react-native/typings/index.d.ts - * @return {Promise} - The utility `await`s promises from `waitFor` so it's `async`. + * @return {Promise} - A Promise that resolves to the original element being interacted with if all steps resolve. */ -const interactionWithUIElement = async ( steps, element ) => { - for ( const step of steps ) { - const { find = {}, then = {} } = step; - const { event = '' } = then; - const [ query = '' ] = - Object.keys( find ).filter( ( key ) => !! find[ key ] ) || []; - const selector = find[ query ] || ''; - const subject = await waitFor( () => element[ query ]( selector ) ); - fireEvent[ event ]( subject ); +const interactionWithUIElement = ( steps, element ) => { + const [ step = {}, ...rest ] = steps; + const { find = {}, then = {} } = step; + const { event = 'none', logBeforeEvent = false } = then; + const toQuery = ( query ) => !! find[ query ]; + const [ query = '' ] = Object.keys( find ).filter( toQuery ); + const selector = find[ query ] || ''; + + if ( logBeforeEvent ) { + // eslint-disable-next-line no-console + console.log( JSON.stringify( element.toJSON(), null, 1 ) ); } + + return new Promise( ( resolve, reject ) => { + waitFor( () => element[ query ]( selector ) ) + .then( ( subject ) => { + if ( event !== 'none' ) { + fireEvent[ event ]( subject ); + } + if ( rest.length ) { + return interactionWithUIElement( rest, element ); + } + } ) + .then( () => resolve( element ) ) + .catch( ( { errorStep = step } ) => reject( { errorStep } ) ); + } ).catch( ( { errorStep } ) => { + // eslint-disable-next-line no-console + console.error( + new Error( ` + Step: + ${ JSON.stringify( errorStep ) } + + Element: + ${ JSON.stringify( element.toJSON() ) } + ` ) + ); + } ); }; /** @@ -138,7 +172,7 @@ describe.each( [ // Arrange const url = 'https://tonytahmouchtest.files.wordpress.com'; const subject = await initializeEditor( { initialHtml } ); - Clipboard.getString.mockReturnValueOnce( url ); + Clipboard.getString.mockReturnValue( url ); // Act const buttonBlockSelector = @@ -164,7 +198,8 @@ describe.each( [ // Assert const expectation = - 'The URL from the Clipboard should NOT be displayed in the Link Settings > Link To field.'; + 'The URL from the Clipboard should NOT be displayed in the ' + + 'Link Settings > Link To field.'; waitFor( () => subject.getByText( url ), { timeout: 50, interval: 10, @@ -172,4 +207,128 @@ describe.each( [ .then( () => done.fail( expectation ) ) .catch( () => done() ); } ); + + /** + * GIVEN a SETTINGS BOTTOM SHEET is displayed; + * GIVEN the CLIPBOARD has a URL copied; + * GIVEN the STATE has NO URL; + * WHEN the USER selects the LINK TO cell; + */ + it( + 'should display the LINK PICKER with the FROM CLIPBOARD CELL populated' + + ' with the URL from the CLIPBOARD.', + // eslint-disable-next-line jest/no-done-callback + async ( done ) => { + // Arrange + const url = 'https://tonytahmouchtest.files.wordpress.com'; + const subject = await initializeEditor( { initialHtml } ); + Clipboard.getString.mockReturnValue( url ); + + // Act + const buttonBlockSelector = + type === 'core/button' ? 'Button Block. Row 1' : undefined; + const imageBlockSelector = + type === 'core/image' + ? 'Double tap and hold to edit' + : undefined; + await interactionWithUIElement( + [ + { + find: { + getByA11yLabel: buttonBlockSelector, + getByA11yHint: imageBlockSelector, + }, + then: { event: 'press' }, + }, + { + find: { getByA11yLabel: 'Open Settings' }, + then: { event: 'press' }, + }, + { + find: { getByA11yLabel: 'Link to, Search or type URL' }, + then: { event: 'press' }, + }, + { + find: { + getByA11yLabel: `Copy URL from the clipboard, ${ url }`, + }, + }, + ], + subject + ); + + // Assert + try { + await waitFor( () => subject.getByText( url ) ); + await waitFor( () => + subject.getByText( __( 'From clipboard' ) ) + ); + done(); + } catch ( error ) { + done.fail( + 'The URL from the Clipboard SHOULD be displayed in the ' + + 'Link Settings > Link To > Clipboard Link Suggestion field like this:' + + ` + ${ url } + ${ __( 'From clipboard' ) } + ` + ); + } + } + ); + + /** + * GIVEN a SETTINGS BOTTOM SHEET is displayed; + * GIVEN the CLIPBOARD has a NON-URL copied; + * GIVEN the STATE has NO URL; + * WHEN the USER selects the LINK TO cell; + */ + // eslint-disable-next-line jest/no-done-callback + it( 'should display the LINK PICKER with NO FROM CLIPBOARD CELL.', async ( done ) => { + // Arrange + const url = 'tonytahmouchtest.files.wordpress.com'; + const subject = await initializeEditor( { initialHtml } ); + Clipboard.getString.mockReturnValue( url ); + + // Act + const buttonBlockSelector = + type === 'core/button' ? 'Button Block. Row 1' : undefined; + const imageBlockSelector = + type === 'core/image' ? 'Double tap and hold to edit' : undefined; + await interactionWithUIElement( + [ + { + find: { + getByA11yLabel: buttonBlockSelector, + getByA11yHint: imageBlockSelector, + }, + then: { event: 'press' }, + }, + { + find: { getByA11yLabel: 'Open Settings' }, + then: { event: 'press' }, + }, + { + find: { getByA11yLabel: 'Link to, Search or type URL' }, + then: { event: 'press' }, + }, + { find: { getByA11yLabel: `Apply` } }, + ], + subject + ); + + // Assert + const expectation = + 'The URL from the Clipboard should NOT be displayed in the ' + + 'Link Settings > Link To > Clipboard Link Suggestion field.'; + waitFor( + () => subject.getByA11yLabel( /Copy URL from the clipboard[,]/ ), + { + timeout: 50, + interval: 10, + } + ) + .then( () => done.fail( expectation ) ) + .catch( () => done() ); + } ); } ); diff --git a/packages/components/src/mobile/bottom-sheet/link-suggestion-item-cell.native.js b/packages/components/src/mobile/bottom-sheet/link-suggestion-item-cell.native.js index c38a47fe687ce8..41a62ec55b4a9d 100644 --- a/packages/components/src/mobile/bottom-sheet/link-suggestion-item-cell.native.js +++ b/packages/components/src/mobile/bottom-sheet/link-suggestion-item-cell.native.js @@ -7,7 +7,7 @@ import { Text, View } from 'react-native'; * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { globe } from '@wordpress/icons'; +import { clipboard, globe } from '@wordpress/icons'; import { usePreferredColorSchemeStyle } from '@wordpress/compose'; /** @@ -20,12 +20,15 @@ import { posts, pages, empty } from '../gridicons'; const icons = { URL: globe, + clipboard, post: posts, page: pages, }; const getSummaryForType = ( type ) => { switch ( type ) { + case 'clipboard': + return __( 'From clipboard' ); case 'mailto': return __( 'Add this email link' ); case 'tel': @@ -36,7 +39,7 @@ const getSummaryForType = ( type ) => { }; // we use some Cell styles here with a column flex-direction -function LinkSuggestionItemCell( { suggestion, onLinkPicked } ) { +function LinkSuggestionItemCell( { suggestion, onLinkPicked, ...props } ) { const { title: contentTitle, url, type, isDirectEntry } = suggestion; const title = isDirectEntry ? url : contentTitle; const summary = isDirectEntry ? getSummaryForType( type ) : url; @@ -58,6 +61,7 @@ function LinkSuggestionItemCell( { suggestion, onLinkPicked } ) { return ( { }; }; +const getURLFromClipboard = async () => { + const text = await Clipboard.getString(); + return !! text && isURL( text ) ? text : ''; +}; + export const LinkPicker = ( { value: initialValue, onLinkPicked, onCancel: cancel, } ) => { - const [ value, setValue ] = useState( initialValue ); + const [ { value, clipboardUrl }, setValue ] = useState( { + value: initialValue, + clipboardUrl: '', + } ); const directEntry = createDirectEntry( value ); // the title of a direct entry is displayed as the raw input value, but if we @@ -66,7 +74,7 @@ export const LinkPicker = ( { }; const clear = () => { - setValue( '' ); + setValue( { value: '', clipboardUrl } ); }; const omniCellStyle = usePreferredColorSchemeStyle( @@ -79,6 +87,14 @@ export const LinkPicker = ( { styles.iconDark ); + useEffect( () => { + getURLFromClipboard() + .then( ( url ) => setValue( { value, clipboardUrl: url } ) ) + .catch( () => setValue( { value, clipboardUrl: '' } ) ); + }, [] ); + + // TODO: Localize the accessibility label. + // TODO: Decide on if `LinkSuggestionItemCell` with `isDirectEntry` makes sense. return ( @@ -96,7 +112,9 @@ export const LinkPicker = ( { autoCapitalize="none" autoCorrect={ false } keyboardType="url" - onChangeValue={ setValue } + onChangeValue={ ( newValue ) => { + setValue( { value: newValue, clipboardUrl } ); + } } onSubmit={ onSubmit } /* eslint-disable-next-line jsx-a11y/no-autofocus */ autoFocus @@ -115,6 +133,24 @@ export const LinkPicker = ( { ) } + { !! clipboardUrl && ( + undefined } + /> + ) } { !! value && ( + + + + + + + + + + + + +); From 7ebf07431c57118d6f84d3872aa95ebb3e0bbc9b Mon Sep 17 00:00:00 2001 From: ttahmouch Date: Wed, 3 Nov 2021 15:25:25 -0400 Subject: [PATCH 06/10] TT | 4058 | Moving the integration tests for the `` from the `` Block Library module as they are not specific to the Image Edit Block but also the Button Edit Block. --- .../src/mobile/link-settings}/test/edit.native.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename packages/{block-library/src/image => components/src/mobile/link-settings}/test/edit.native.js (100%) diff --git a/packages/block-library/src/image/test/edit.native.js b/packages/components/src/mobile/link-settings/test/edit.native.js similarity index 100% rename from packages/block-library/src/image/test/edit.native.js rename to packages/components/src/mobile/link-settings/test/edit.native.js From 4695c8039fd264a520d05a2e1e6c31db1ff60e64 Mon Sep 17 00:00:00 2001 From: ttahmouch Date: Wed, 10 Nov 2021 13:53:05 -0500 Subject: [PATCH 07/10] TT | 4058 | Tested scenarios for when the Clipboard Link Suggestion is hidden or pressed in the Link Picker. 0. Hiding the Clipboard Link Suggestion. Previously, the Clipboard Link Suggestion was only being hidden in the Link Picker if the text in the Clipboard was NOT a valid URL. Now, the Clipboard Link Suggestion will also be hidden in the Link Picker if the text is a valid URL, but is the same URL the user already picked. 1. Pressing the Clipboard Link Suggestion. Previously, this was unimplemented. Now, the behavior is identical to selecting a Link Suggestion from the search results in the Link Picker, i.e., it will navigate back to the previous screen and pass it the selected URL to change the Block Attributes. --- .../src/mobile/link-picker/index.native.js | 4 +- .../mobile/link-settings/test/edit.native.js | 443 +++++++++++++----- 2 files changed, 318 insertions(+), 129 deletions(-) diff --git a/packages/components/src/mobile/link-picker/index.native.js b/packages/components/src/mobile/link-picker/index.native.js index 0bb9e0f8764ec8..b1ae9853982329 100644 --- a/packages/components/src/mobile/link-picker/index.native.js +++ b/packages/components/src/mobile/link-picker/index.native.js @@ -133,7 +133,7 @@ export const LinkPicker = ( { ) } - { !! clipboardUrl && ( + { !! clipboardUrl && clipboardUrl !== value && ( undefined } + onLinkPicked={ pickLink } /> ) } { !! value && ( diff --git a/packages/components/src/mobile/link-settings/test/edit.native.js b/packages/components/src/mobile/link-settings/test/edit.native.js index fea96d2e9c5150..c211b8560ad796 100644 --- a/packages/components/src/mobile/link-settings/test/edit.native.js +++ b/packages/components/src/mobile/link-settings/test/edit.native.js @@ -25,28 +25,31 @@ const unregisterBlocks = () => { }; /** - * ``` + * ### TODO + * + Try to figure out why I can't `console.log(JSON.stringify(subject.toJSON()))` anymore. + * + * ### DONE + * * GIVEN the Link Settings sheet displays, * AND the Clipboard has a URL copied, * THEN the `Link to` field in the Link Settings should be populated with the placeholder text, i.e., `Search or type URL`. * - * GIVEN the Link Picker sheet displays, - * AND the Clipboard has a URL copied that is different from the contents of the text input field, - * THEN the `From Clipboard` table cell should be populated with the URL from the Clipboard. - * * GIVEN the Link Picker sheet displays with the `From Clipboard` table cell, * WHEN the Clipboard is cleared or changed to something that is NOT a URL, * THEN the `From Clipboard` table cell should disappear. * * GIVEN the Link Picker sheet displays, - * AND the contents of the Clipboard are IDENTICAL to the contents of the text input field, - * THEN do NOT display the `From Clipboard` table cell. + * AND the Clipboard has a URL copied that is different from the contents of the text input field, + * THEN the `From Clipboard` table cell should be populated with the URL from the Clipboard. * * GIVEN the Link Picker sheet displays, * AND the `From Clipboard` table cell is pressed, * THEN the `Search or type URL` text input field is populated with the URL from the Clipboard, * AND the `Add this link` table cell is repopulated with the new URL from the Clipboard. - * ``` + * + * GIVEN the Link Picker sheet displays, + * AND the contents of the Clipboard are IDENTICAL to the contents of the text input field, + * THEN do NOT display the `From Clipboard` table cell. */ describe.each( [ [ @@ -93,7 +96,7 @@ describe.each( [ it( 'should display the LINK SETTINGS with an EMPTY LINK TO field.', async ( done ) => { // Arrange const expectation = - 'The LINK SETTINGS > LINK TO field should be displayed WITHOUT a URL from the CLIPBOARD.'; + 'The LINK SETTINGS > LINK TO field SHOULD be displayed WITHOUT a URL from the CLIPBOARD.'; const url = 'https://tonytahmouchtest.files.wordpress.com'; const subject = await initializeEditor( { initialHtml } ); Clipboard.getString.mockReturnValue( url ); @@ -129,138 +132,324 @@ describe.each( [ } } ); - /** - * GIVEN a SETTINGS BOTTOM SHEET is displayed; - * GIVEN the CLIPBOARD has a URL copied; - * GIVEN the STATE has NO URL; - * WHEN the USER selects the LINK TO cell; - */ - it( - 'should display the LINK PICKER with the FROM CLIPBOARD CELL populated' + - ' with the URL from the CLIPBOARD.', - // eslint-disable-next-line jest/no-done-callback - async ( done ) => { - // Arrange - const url = 'https://tonytahmouchtest.files.wordpress.com'; - const expectation = - 'The LINK SETTINGS > LINK TO > LINK SUGGESTION SHOULD suggest the URL from the CLIPBOARD, e.g.,' + - ` - ${ url } - ${ __( 'From clipboard' ) } - `; - const subject = await initializeEditor( { initialHtml } ); - Clipboard.getString.mockReturnValue( url ); + describe( '', () => { + describe( 'Hide Clipboard Link Suggestion - Invalid URL in Clipboard', () => { + /** + * GIVEN a SETTINGS BOTTOM SHEET is displayed; + * GIVEN the CLIPBOARD has a NON-URL copied; + * GIVEN the STATE has NO URL; + * WHEN the USER selects the LINK TO cell; + */ + // eslint-disable-next-line jest/no-done-callback + it( 'should display the LINK PICKER with NO FROM CLIPBOARD CELL.', async ( done ) => { + // Arrange + const expectation = + 'The LINK PICKER > LINK SUGGESTION SHOULD NOT suggest the URL from the CLIPBOARD.'; + const url = 'tonytahmouchtest.files.wordpress.com'; + const subject = await initializeEditor( { initialHtml } ); + Clipboard.getString.mockReturnValue( url ); - // Act - try { - const block = await waitFor( () => - subject.getByA11yLabel( - type === 'core/image' ? /Image Block/ : /Button Block/ - ) - ); - fireEvent.press( block ); - fireEvent.press( block ); - fireEvent.press( - await waitFor( () => - subject.getByA11yLabel( 'Open Settings' ) - ) - ); - fireEvent.press( - await waitFor( () => + // Act + try { + const block = await waitFor( () => subject.getByA11yLabel( - `Link to, ${ - type === 'core/image' - ? 'None' - : 'Search or type URL' - }` + type === 'core/image' + ? /Image Block/ + : /Button Block/ + ) + ); + fireEvent.press( block ); + fireEvent.press( block ); + fireEvent.press( + await waitFor( () => + subject.getByA11yLabel( 'Open Settings' ) ) - ) - ); - if ( type === 'core/image' ) { + ); fireEvent.press( await waitFor( () => - subject.getByA11yLabel( /Custom URL/ ) + subject.getByA11yLabel( + `Link to, ${ + type === 'core/image' + ? 'None' + : 'Search or type URL' + }` + ) ) ); + if ( type === 'core/image' ) { + fireEvent.press( + await waitFor( () => + subject.getByA11yLabel( /Custom URL/ ) + ) + ); + } + await waitFor( () => subject.getByA11yLabel( 'Apply' ) ); + } catch ( error ) { + done.fail( error ); } - await waitFor( () => - subject.getByA11yLabel( - `Copy URL from the clipboard, ${ url }` - ) - ); - } catch ( error ) { - done.fail( error ); - } - // Assert - try { - await waitFor( () => subject.getByText( url ) ); - await waitFor( () => - subject.getByText( __( 'From clipboard' ) ) - ); - done(); - } catch ( error ) { - done.fail( expectation ); - } - } - ); + // Assert + waitFor( + () => + subject.getByA11yLabel( + /Copy URL from the clipboard[,]/ + ), + { timeout: 50, interval: 10 } + ) + .then( () => done.fail( expectation ) ) + .catch( () => done() ); + } ); + } ); - /** - * GIVEN a SETTINGS BOTTOM SHEET is displayed; - * GIVEN the CLIPBOARD has a NON-URL copied; - * GIVEN the STATE has NO URL; - * WHEN the USER selects the LINK TO cell; - */ - // eslint-disable-next-line jest/no-done-callback - it( 'should display the LINK PICKER with NO FROM CLIPBOARD CELL.', async ( done ) => { - // Arrange - const expectation = - 'The LINK SETTINGS > LINK TO > LINK SUGGESTION SHOULD NOT suggest the URL from the CLIPBOARD.'; - const url = 'tonytahmouchtest.files.wordpress.com'; - const subject = await initializeEditor( { initialHtml } ); - Clipboard.getString.mockReturnValue( url ); + describe( 'Hide Clipboard Link Suggestion - Valid and Same URL in Clipboard', () => { + /** + * GIVEN a SETTINGS BOTTOM SHEET is displayed; + * GIVEN the CLIPBOARD has a URL copied; + * GIVEN the STATE has the SAME URL as the CLIPBOARD; + * WHEN the USER selects the LINK TO cell; + */ + // eslint-disable-next-line jest/no-done-callback + it( 'should display the LINK PICKER with NO FROM CLIPBOARD CELL.', async ( done ) => { + // Arrange + const expectation = + 'The LINK PICKER > LINK SUGGESTION SHOULD NOT suggest the URL from the CLIPBOARD.'; + const url = 'https://tonytahmouchtest.files.wordpress.com'; + const subject = await initializeEditor( { initialHtml } ); + Clipboard.getString.mockReturnValue( url ); - // Act - try { - const block = await waitFor( () => - subject.getByA11yLabel( - type === 'core/image' ? /Image Block/ : /Button Block/ - ) - ); - fireEvent.press( block ); - fireEvent.press( block ); - fireEvent.press( - await waitFor( () => subject.getByA11yLabel( 'Open Settings' ) ) - ); - fireEvent.press( - await waitFor( () => - subject.getByA11yLabel( - `Link to, ${ + // Act + try { + const block = await waitFor( () => + subject.getByA11yLabel( type === 'core/image' - ? 'None' - : 'Search or type URL' - }` - ) + ? /Image Block/ + : /Button Block/ + ) + ); + fireEvent.press( block ); + fireEvent.press( block ); + fireEvent.press( + await waitFor( () => + subject.getByA11yLabel( 'Open Settings' ) + ) + ); + fireEvent.press( + await waitFor( () => + subject.getByA11yLabel( + `Link to, ${ + type === 'core/image' + ? 'None' + : 'Search or type URL' + }` + ) + ) + ); + if ( type === 'core/image' ) { + fireEvent.press( + await waitFor( () => + subject.getByA11yLabel( 'Custom URL. Empty' ) + ) + ); + } + fireEvent.press( + await waitFor( () => + subject.getByA11yLabel( + `Copy URL from the clipboard, ${ url }` + ) + ) + ); + fireEvent.press( + await waitFor( () => + subject.getByA11yLabel( + `Link to, ${ + type === 'core/image' ? 'Custom URL' : url + }` + ) + ) + ); + if ( type === 'core/image' ) { + fireEvent.press( + await waitFor( () => + subject.getByA11yLabel( `Custom URL, ${ url }` ) + ) + ); + } + await waitFor( () => subject.getByA11yLabel( 'Apply' ) ); + } catch ( error ) { + done.fail( error ); + } + + // Assert + waitFor( + () => + subject.getByA11yLabel( + /Copy URL from the clipboard[,]/ + ), + { timeout: 50, interval: 10 } ) + .then( () => done.fail( expectation ) ) + .catch( () => done() ); + } ); + } ); + + describe( 'Show Clipboard Link Suggestion - Valid and Different URL in Clipboard', () => { + /** + * GIVEN a SETTINGS BOTTOM SHEET is displayed; + * GIVEN the CLIPBOARD has a URL copied; + * GIVEN the STATE has NO URL; + * WHEN the USER selects the LINK TO cell; + */ + it( + 'should display the LINK PICKER with the FROM CLIPBOARD CELL populated' + + ' with the URL from the CLIPBOARD.', + // eslint-disable-next-line jest/no-done-callback + async ( done ) => { + // Arrange + const url = 'https://tonytahmouchtest.files.wordpress.com'; + const expectation = + 'The LINK PICKER > LINK SUGGESTION SHOULD suggest the URL from the CLIPBOARD, e.g.,' + + ` + ${ url } + ${ __( 'From clipboard' ) } + `; + const subject = await initializeEditor( { initialHtml } ); + Clipboard.getString.mockReturnValue( url ); + + // Act + try { + const block = await waitFor( () => + subject.getByA11yLabel( + type === 'core/image' + ? /Image Block/ + : /Button Block/ + ) + ); + fireEvent.press( block ); + fireEvent.press( block ); + fireEvent.press( + await waitFor( () => + subject.getByA11yLabel( 'Open Settings' ) + ) + ); + fireEvent.press( + await waitFor( () => + subject.getByA11yLabel( + `Link to, ${ + type === 'core/image' + ? 'None' + : 'Search or type URL' + }` + ) + ) + ); + if ( type === 'core/image' ) { + fireEvent.press( + await waitFor( () => + subject.getByA11yLabel( /Custom URL/ ) + ) + ); + } + await waitFor( () => + subject.getByA11yLabel( + `Copy URL from the clipboard, ${ url }` + ) + ); + } catch ( error ) { + done.fail( error ); + } + + // Assert + try { + await waitFor( () => subject.getByText( url ) ); + await waitFor( () => + subject.getByText( __( 'From clipboard' ) ) + ); + done(); + } catch ( error ) { + done.fail( expectation ); + } + } ); - if ( type === 'core/image' ) { - fireEvent.press( - await waitFor( () => - subject.getByA11yLabel( /Custom URL/ ) - ) - ); - } - await waitFor( () => subject.getByA11yLabel( 'Apply' ) ); - } catch ( error ) { - done.fail( error ); - } + } ); - // Assert - waitFor( - () => subject.getByA11yLabel( /Copy URL from the clipboard[,]/ ), - { timeout: 50, interval: 10 } - ) - .then( () => done.fail( expectation ) ) - .catch( () => done() ); + describe( 'Press Clipboard Link Suggestion', () => { + /** + * GIVEN a LINK PICKER SHEET is displayed; + * GIVEN the FROM CLIPBOARD CELL is displayed; + * WHEN the FROM CLIPBOARD CELL is pressed; + */ + it( + 'should display the LINK SETTINGS with the URL from the CLIPBOARD' + + 'populated in the LINK TO field.', + // eslint-disable-next-line jest/no-done-callback + async ( done ) => { + // Arrange + const expectation = + 'The LINK SETTINGS > LINK TO field SHOULD be displayed WITH a URL from the CLIPBOARD.'; + const url = 'https://tonytahmouchtest.files.wordpress.com'; + const subject = await initializeEditor( { initialHtml } ); + Clipboard.getString.mockReturnValue( url ); + + // Act + try { + const block = await waitFor( () => + subject.getByA11yLabel( + type === 'core/image' + ? /Image Block/ + : /Button Block/ + ) + ); + fireEvent.press( block ); + fireEvent.press( block ); + fireEvent.press( + await waitFor( () => + subject.getByA11yLabel( 'Open Settings' ) + ) + ); + fireEvent.press( + await waitFor( () => + subject.getByA11yLabel( + `Link to, ${ + type === 'core/image' + ? 'None' + : 'Search or type URL' + }` + ) + ) + ); + if ( type === 'core/image' ) { + fireEvent.press( + await waitFor( () => + subject.getByA11yLabel( /Custom URL/ ) + ) + ); + } + fireEvent.press( + await waitFor( () => + subject.getByA11yLabel( + `Copy URL from the clipboard, ${ url }` + ) + ) + ); + } catch ( error ) { + done.fail( error ); + } + + // Assert + try { + await waitFor( () => + subject.getByA11yLabel( + `Link to, ${ + type === 'core/image' ? 'Custom URL' : url + }` + ) + ); + done(); + } catch ( error ) { + done.fail( expectation ); + } + } + ); + } ); } ); } ); From 5bf30388ecf5c22493255f354181745fd19fba14 Mon Sep 17 00:00:00 2001 From: ttahmouch Date: Tue, 16 Nov 2021 11:37:07 -0500 Subject: [PATCH 08/10] TT | 4058 | Cleaning up comments and console logs in the test module. --- .../mobile/link-settings/test/edit.native.js | 25 +------------------ 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/packages/components/src/mobile/link-settings/test/edit.native.js b/packages/components/src/mobile/link-settings/test/edit.native.js index c211b8560ad796..13b8001c388e4a 100644 --- a/packages/components/src/mobile/link-settings/test/edit.native.js +++ b/packages/components/src/mobile/link-settings/test/edit.native.js @@ -27,29 +27,6 @@ const unregisterBlocks = () => { /** * ### TODO * + Try to figure out why I can't `console.log(JSON.stringify(subject.toJSON()))` anymore. - * - * ### DONE - * - * GIVEN the Link Settings sheet displays, - * AND the Clipboard has a URL copied, - * THEN the `Link to` field in the Link Settings should be populated with the placeholder text, i.e., `Search or type URL`. - * - * GIVEN the Link Picker sheet displays with the `From Clipboard` table cell, - * WHEN the Clipboard is cleared or changed to something that is NOT a URL, - * THEN the `From Clipboard` table cell should disappear. - * - * GIVEN the Link Picker sheet displays, - * AND the Clipboard has a URL copied that is different from the contents of the text input field, - * THEN the `From Clipboard` table cell should be populated with the URL from the Clipboard. - * - * GIVEN the Link Picker sheet displays, - * AND the `From Clipboard` table cell is pressed, - * THEN the `Search or type URL` text input field is populated with the URL from the Clipboard, - * AND the `Add this link` table cell is repopulated with the new URL from the Clipboard. - * - * GIVEN the Link Picker sheet displays, - * AND the contents of the Clipboard are IDENTICAL to the contents of the text input field, - * THEN do NOT display the `From Clipboard` table cell. */ describe.each( [ [ @@ -380,7 +357,7 @@ describe.each( [ */ it( 'should display the LINK SETTINGS with the URL from the CLIPBOARD' + - 'populated in the LINK TO field.', + ' populated in the LINK TO field.', // eslint-disable-next-line jest/no-done-callback async ( done ) => { // Arrange From 1530f5e8f2be9ff7a41effc8dec43d5457356453 Mon Sep 17 00:00:00 2001 From: ttahmouch Date: Tue, 16 Nov 2021 11:41:46 -0500 Subject: [PATCH 09/10] TT | 4058 | Replacing the placeholder Clipboard SVG with one from GridIcons. --- .../link-suggestion-item-cell.native.js | 4 ++-- .../src/mobile/gridicons/index.native.js | 6 +++--- packages/icons/src/index.js | 1 - packages/icons/src/library/clipboard.js | 20 ------------------- 4 files changed, 5 insertions(+), 26 deletions(-) delete mode 100644 packages/icons/src/library/clipboard.js diff --git a/packages/components/src/mobile/bottom-sheet/link-suggestion-item-cell.native.js b/packages/components/src/mobile/bottom-sheet/link-suggestion-item-cell.native.js index 41a62ec55b4a9d..1b850e0e0f2008 100644 --- a/packages/components/src/mobile/bottom-sheet/link-suggestion-item-cell.native.js +++ b/packages/components/src/mobile/bottom-sheet/link-suggestion-item-cell.native.js @@ -7,7 +7,7 @@ import { Text, View } from 'react-native'; * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { clipboard, globe } from '@wordpress/icons'; +import { globe } from '@wordpress/icons'; import { usePreferredColorSchemeStyle } from '@wordpress/compose'; /** @@ -16,7 +16,7 @@ import { usePreferredColorSchemeStyle } from '@wordpress/compose'; import Cell from './cell'; import cellStyles from './styles.scss'; import suggestionStyles from './link-suggestion-styles.scss'; -import { posts, pages, empty } from '../gridicons'; +import { posts, pages, empty, clipboard } from '../gridicons'; const icons = { URL: globe, diff --git a/packages/components/src/mobile/gridicons/index.native.js b/packages/components/src/mobile/gridicons/index.native.js index 43e33862dfc311..8f4dd826a0a788 100644 --- a/packages/components/src/mobile/gridicons/index.native.js +++ b/packages/components/src/mobile/gridicons/index.native.js @@ -9,6 +9,9 @@ const fromPathData24x24 = ( pathData ) => ( ); +export const clipboard = fromPathData24x24( + 'M16 18H8v-2h8v2zm0-6H8v2h8v-2zm2-9h-2v2h2v15H6V5h2V3H6a2 2 0 00-2 2v15a2 2 0 002 2h12a2 2 0 002-2V5a2 2 0 00-2-2zm-4 2V4a2 2 0 10-4 0v1a2 2 0 00-2 2v1h8V7a2 2 0 00-2-2z' +); export const posts = fromPathData24x24( 'M16 19H3v-2h13v2zm5-10H3v2h18V9zM3 5v2h11V5H3zm14 0v2h4V5h-4zm-6 8v2h10v-2H11zm-8 0v2h5v-2H3z' ); @@ -21,15 +24,12 @@ export const refresh = fromPathData24x24( export const noticeOutline = fromPathData24x24( 'M12 4c4.41 0 8 3.59 8 8s-3.59 8-8 8-8-3.59-8-8 3.59-8 8-8m0-2C6.477 2 2 6.477 2 12s4.477 10 10 10 10-4.477 10-10S17.523 2 12 2zm1 13h-2v2h2v-2zm-2-2h2l.5-6h-3l.5 6z' ); - export const empty = ( ); - export const search = fromPathData24x24( 'M21,19l-5.154-5.154C16.574,12.742,17,11.421,17,10c0-3.866-3.134-7-7-7s-7,3.134-7,7c0,3.866,3.134,7,7,7 c1.421,0,2.742-0.426,3.846-1.154L19,21L21,19z M5,10c0-2.757,2.243-5,5-5s5,2.243,5,5s-2.243,5-5,5S5,12.757,5,10z' ); - export default { empty, posts, diff --git a/packages/icons/src/index.js b/packages/icons/src/index.js index 6456528616b6dd..7627038596b68a 100644 --- a/packages/icons/src/index.js +++ b/packages/icons/src/index.js @@ -38,7 +38,6 @@ export { default as chevronRight } from './library/chevron-right'; export { default as chevronRightSmall } from './library/chevron-right-small'; export { default as chevronUp } from './library/chevron-up'; export { default as classic } from './library/classic'; -export { default as clipboard } from './library/clipboard'; export { default as close } from './library/close'; export { default as closeSmall } from './library/close-small'; export { default as cloudUpload } from './library/cloud-upload'; diff --git a/packages/icons/src/library/clipboard.js b/packages/icons/src/library/clipboard.js deleted file mode 100644 index 1e006d7e6418d6..00000000000000 --- a/packages/icons/src/library/clipboard.js +++ /dev/null @@ -1,20 +0,0 @@ -/** - * WordPress dependencies - */ -import { G, Path, Rect, SVG } from '@wordpress/primitives'; - -export default ( - - - - - - - - - - - - - -); From 40f5004851908960a835a013086b56f56cb25cb4 Mon Sep 17 00:00:00 2001 From: ttahmouch Date: Tue, 16 Nov 2021 12:06:58 -0500 Subject: [PATCH 10/10] TT | 4058 | Updated the `accessibilityLabel` of the Clipboard `` to be localized. The tests still default to the `English` translation. --- .../src/mobile/link-picker/index.native.js | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/packages/components/src/mobile/link-picker/index.native.js b/packages/components/src/mobile/link-picker/index.native.js index b1ae9853982329..f27702f3393c24 100644 --- a/packages/components/src/mobile/link-picker/index.native.js +++ b/packages/components/src/mobile/link-picker/index.native.js @@ -8,7 +8,7 @@ import { lowerCase, startsWith } from 'lodash'; * WordPress dependencies */ import { useEffect, useState } from '@wordpress/element'; -import { __ } from '@wordpress/i18n'; +import { __, sprintf } from '@wordpress/i18n'; import { BottomSheet, Icon } from '@wordpress/components'; import { getProtocol, isURL, prependHTTP } from '@wordpress/url'; import { link, cancelCircleFilled } from '@wordpress/icons'; @@ -136,13 +136,11 @@ export const LinkPicker = ( { { !! clipboardUrl && clipboardUrl !== value && (