diff --git a/README.md b/README.md index 18db1bb0fc..43d2bde6c4 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +[![Coverage Status](https://coveralls.io/repos/github/magento/pwa-studio/badge.svg?branch=develop)](https://coveralls.io/github/magento/pwa-studio?branch=develop) + # PWA Studio Magento PWA Studio is a collection of tools that lets developers build complex Progressive Web Applications on top of Magento 2 stores. diff --git a/packages/peregrine/lib/talons/Header/__tests__/__snapshots__/useStoreSwitcher.spec.js.snap b/packages/peregrine/lib/talons/Header/__tests__/__snapshots__/useStoreSwitcher.spec.js.snap index 345d89a96a..307fb9cd0c 100644 --- a/packages/peregrine/lib/talons/Header/__tests__/__snapshots__/useStoreSwitcher.spec.js.snap +++ b/packages/peregrine/lib/talons/Header/__tests__/__snapshots__/useStoreSwitcher.spec.js.snap @@ -5,62 +5,171 @@ Object { "availableStores": Map { "store1" => Object { "category_url_suffix": null, + "code": "store1", "currency": "USD", "isCurrent": false, "locale": "locale1", "product_url_suffix": null, "secure_base_media_url": "https://example.com/media/", + "sortOrder": 0, + "storeGroupCode": "group1", + "storeGroupName": "Group 1", "storeName": "Store 1", }, "store2" => Object { "category_url_suffix": ".html", + "code": "store2", "currency": "EUR", "isCurrent": true, "locale": "locale2", "product_url_suffix": ".html", "secure_base_media_url": "https://cdn.origin:9000/media/custom/", + "sortOrder": 1, + "storeGroupCode": "group1", + "storeGroupName": "Group 1", "storeName": "Store 2", }, "store3" => Object { "category_url_suffix": null, + "code": "store3", "currency": "EUR", "isCurrent": false, "locale": "locale3", "product_url_suffix": ".htm", "secure_base_media_url": "https://example.com/media/", + "sortOrder": 2, + "storeGroupCode": "group1", + "storeGroupName": "Group 1", "storeName": "Store 3", }, "store4" => Object { "category_url_suffix": ".htm", + "code": "store4", "currency": "EUR", "isCurrent": false, "locale": "locale4", "product_url_suffix": null, "secure_base_media_url": "https://example.com/media/", + "sortOrder": 0, + "storeGroupCode": "group2", + "storeGroupName": "Group 2", "storeName": "Store 4", }, "store5" => Object { "category_url_suffix": "-abc1", + "code": "store5", "currency": "EUR", "isCurrent": false, "locale": "locale5", "product_url_suffix": ".htm.htm", "secure_base_media_url": "https://example.com/media/", + "sortOrder": 1, + "storeGroupCode": "group2", + "storeGroupName": "Group 2", "storeName": "Store 5", }, "store6" => Object { "category_url_suffix": ".some.some", + "code": "store6", "currency": "EUR", "isCurrent": false, "locale": "locale6", "product_url_suffix": "-123abc", "secure_base_media_url": "https://example.com/media/", + "sortOrder": 2, + "storeGroupCode": "group2", + "storeGroupName": "Group 2", "storeName": "Store 6", }, }, + "currentGroupName": "Group 1", "currentStoreName": "Store 2", "handleSwitchStore": [Function], "handleTriggerClick": [Function], + "storeGroups": Map { + "group1" => Array [ + Object { + "category_url_suffix": null, + "code": "store1", + "currency": "USD", + "isCurrent": false, + "locale": "locale1", + "product_url_suffix": null, + "secure_base_media_url": "https://example.com/media/", + "sortOrder": 0, + "storeGroupCode": "group1", + "storeGroupName": "Group 1", + "storeName": "Store 1", + }, + Object { + "category_url_suffix": ".html", + "code": "store2", + "currency": "EUR", + "isCurrent": true, + "locale": "locale2", + "product_url_suffix": ".html", + "secure_base_media_url": "https://cdn.origin:9000/media/custom/", + "sortOrder": 1, + "storeGroupCode": "group1", + "storeGroupName": "Group 1", + "storeName": "Store 2", + }, + Object { + "category_url_suffix": null, + "code": "store3", + "currency": "EUR", + "isCurrent": false, + "locale": "locale3", + "product_url_suffix": ".htm", + "secure_base_media_url": "https://example.com/media/", + "sortOrder": 2, + "storeGroupCode": "group1", + "storeGroupName": "Group 1", + "storeName": "Store 3", + }, + ], + "group2" => Array [ + Object { + "category_url_suffix": ".htm", + "code": "store4", + "currency": "EUR", + "isCurrent": false, + "locale": "locale4", + "product_url_suffix": null, + "secure_base_media_url": "https://example.com/media/", + "sortOrder": 0, + "storeGroupCode": "group2", + "storeGroupName": "Group 2", + "storeName": "Store 4", + }, + Object { + "category_url_suffix": "-abc1", + "code": "store5", + "currency": "EUR", + "isCurrent": false, + "locale": "locale5", + "product_url_suffix": ".htm.htm", + "secure_base_media_url": "https://example.com/media/", + "sortOrder": 1, + "storeGroupCode": "group2", + "storeGroupName": "Group 2", + "storeName": "Store 5", + }, + Object { + "category_url_suffix": ".some.some", + "code": "store6", + "currency": "EUR", + "isCurrent": false, + "locale": "locale6", + "product_url_suffix": "-123abc", + "secure_base_media_url": "https://example.com/media/", + "sortOrder": 2, + "storeGroupCode": "group2", + "storeGroupName": "Group 2", + "storeName": "Store 6", + }, + ], + }, "storeMenuIsOpen": false, "storeMenuRef": "elementRef", "storeMenuTriggerRef": [MockFunction], diff --git a/packages/peregrine/lib/talons/Header/__tests__/useStoreSwitcher.spec.js b/packages/peregrine/lib/talons/Header/__tests__/useStoreSwitcher.spec.js index 0099fbcb39..a706c70bab 100644 --- a/packages/peregrine/lib/talons/Header/__tests__/useStoreSwitcher.spec.js +++ b/packages/peregrine/lib/talons/Header/__tests__/useStoreSwitcher.spec.js @@ -70,6 +70,7 @@ const getTalonProps = props => { const storeConfigResponse = { code: 'store2', + store_group_name: 'Group 1', store_name: 'Store 2' }; @@ -87,6 +88,9 @@ const availableStoresResponse = [ { code: 'store1', locale: 'locale1', + store_group_code: 'group1', + store_group_name: 'Group 1', + store_sort_order: 0, store_name: 'Store 1', default_display_currency_code: 'USD', category_url_suffix: null, @@ -96,6 +100,9 @@ const availableStoresResponse = [ { code: 'store2', locale: 'locale2', + store_group_code: 'group1', + store_group_name: 'Group 1', + store_sort_order: 1, store_name: 'Store 2', default_display_currency_code: 'EUR', category_url_suffix: '.html', @@ -105,6 +112,9 @@ const availableStoresResponse = [ { code: 'store3', locale: 'locale3', + store_group_code: 'group1', + store_group_name: 'Group 1', + store_sort_order: 2, store_name: 'Store 3', default_display_currency_code: 'EUR', category_url_suffix: null, @@ -114,6 +124,9 @@ const availableStoresResponse = [ { code: 'store4', locale: 'locale4', + store_group_code: 'group2', + store_group_name: 'Group 2', + store_sort_order: 0, store_name: 'Store 4', default_display_currency_code: 'EUR', category_url_suffix: '.htm', @@ -123,6 +136,9 @@ const availableStoresResponse = [ { code: 'store5', locale: 'locale5', + store_group_code: 'group2', + store_group_name: 'Group 2', + store_sort_order: 1, store_name: 'Store 5', default_display_currency_code: 'EUR', category_url_suffix: '-abc1', @@ -132,6 +148,9 @@ const availableStoresResponse = [ { code: 'store6', locale: 'locale6', + store_group_code: 'group2', + store_group_name: 'Group 2', + store_sort_order: 2, store_name: 'Store 6', default_display_currency_code: 'EUR', category_url_suffix: '.some.some', @@ -159,6 +178,15 @@ test('should return correct shape', () => { const { talonProps } = getTalonProps(defaultProps); expect(talonProps).toMatchSnapshot(); + + expect(talonProps.currentGroupName).toEqual( + storeConfigResponse.store_group_name + ); + + // storeGroups should be a map of the "groups", sorted in sort order. + expect(talonProps.storeGroups.size).toEqual(2); + expect(talonProps.storeGroups.get('group1').length).toEqual(3); + expect(talonProps.storeGroups.get('group2').length).toEqual(3); }); describe('event handlers', () => { diff --git a/packages/peregrine/lib/talons/Header/storeSwitcher.gql.js b/packages/peregrine/lib/talons/Header/storeSwitcher.gql.js index b5f51e38ae..faf0b080b0 100644 --- a/packages/peregrine/lib/talons/Header/storeSwitcher.gql.js +++ b/packages/peregrine/lib/talons/Header/storeSwitcher.gql.js @@ -6,6 +6,7 @@ export const GET_STORE_CONFIG_DATA = gql` id code store_name + store_group_name } } `; @@ -29,7 +30,10 @@ export const GET_AVAILABLE_STORES_DATA = gql` locale product_url_suffix secure_base_media_url + store_group_code + store_group_name store_name + store_sort_order } } `; diff --git a/packages/peregrine/lib/talons/Header/useStoreSwitcher.js b/packages/peregrine/lib/talons/Header/useStoreSwitcher.js index 317df60604..c49e6a3f46 100644 --- a/packages/peregrine/lib/talons/Header/useStoreSwitcher.js +++ b/packages/peregrine/lib/talons/Header/useStoreSwitcher.js @@ -19,17 +19,24 @@ const mapAvailableOptions = (config, stores) => { locale, product_url_suffix, secure_base_media_url, - store_name: storeName + store_group_code: storeGroupCode, + store_group_name: storeGroupName, + store_name: storeName, + store_sort_order: sortOrder } = store; const isCurrent = code === configCode; const option = { category_url_suffix, + code, currency, isCurrent, locale, product_url_suffix, secure_base_media_url, + sortOrder, + storeGroupCode, + storeGroupName, storeName }; @@ -87,6 +94,12 @@ export const useStoreSwitcher = (props = {}) => { } }, [storeConfigData]); + const currentGroupName = useMemo(() => { + if (storeConfigData) { + return storeConfigData.storeConfig.store_group_name; + } + }, [storeConfigData]); + const currentStoreCode = useMemo(() => { if (storeConfigData) { return storeConfigData.storeConfig.code; @@ -99,17 +112,38 @@ export const useStoreSwitcher = (props = {}) => { } }, [urlResolverData]); + // availableStores => mapped options or empty map if undefined. const availableStores = useMemo(() => { return ( - storeConfigData && - availableStoresData && - mapAvailableOptions( - storeConfigData.storeConfig, - availableStoresData.availableStores - ) + (storeConfigData && + availableStoresData && + mapAvailableOptions( + storeConfigData.storeConfig, + availableStoresData.availableStores + )) || + new Map() ); }, [storeConfigData, availableStoresData]); + // Create a map of sorted store views for each group. + const storeGroups = useMemo(() => { + const groups = new Map(); + + availableStores.forEach(store => { + const groupCode = store.storeGroupCode; + if (!groups.has(groupCode)) { + const groupViews = [store]; + groups.set(groupCode, groupViews); + } else { + const groupViews = groups.get(groupCode); + // Insert store at configured position + groupViews.splice(store.sortOrder, 0, store); + } + }); + + return groups; + }, [availableStores]); + // Get pathname with suffix based on page type const getPathname = useCallback( storeCode => { @@ -209,8 +243,10 @@ export const useStoreSwitcher = (props = {}) => { }, [setStoreMenuIsOpen]); return { - currentStoreName, availableStores, + currentGroupName, + currentStoreName, + storeGroups, storeMenuRef, storeMenuTriggerRef, storeMenuIsOpen, diff --git a/packages/pwa-buildpack/lib/WebpackTools/targetables/SingleImportStatement.js b/packages/pwa-buildpack/lib/WebpackTools/targetables/SingleImportStatement.js index c458836fb7..761275cca0 100644 --- a/packages/pwa-buildpack/lib/WebpackTools/targetables/SingleImportStatement.js +++ b/packages/pwa-buildpack/lib/WebpackTools/targetables/SingleImportStatement.js @@ -26,48 +26,11 @@ class SingleImportError extends Error { * * That's _almost_ all we need to do the import management we need, including * deduping and scope conflict resolution. - * - * @example Add two new imports that would set the same local binding. - * - * ```js - * esModule.addImport('import Button from "vendor/button"') - * .addImport('import Button from "different-vendor"'); - * ``` - * - * The two statements refer to different modules, but they are both trying to - * use the local variable name "Button". - * - * SingleImportStatement helps with that by detecting the conflict and then - * _renaming the second binding_, using SingleImportStatement#changeBinding. - * - * Often the developer will want to know what the new binding is, so they can - * refer to the component in code and from other targets. SingleImportStatement - * makes this nice, too. The `TargetableModule#addImport(importString)` method - * **returns the SingleImportStatement it created.** That object has a - * `binding` property, which will equal the new name created by the conflict - * resolution. You can then use it in templates. - * - * @example Add an import and then some code which uses the imported module. - * - * ```js - * const logger = esModule.addImport('logger from './logger'); - * esModule.insertAfterSource("./logger';\n", `${logger.binding}('startup')`) - * ``` - * If `logger` is changed due to conflict to a unique name like 'logger$$2', - * then `logger.binding` will be equal to `logger$$2`. _Note: - * SingleImportStatement overrides its `toString` method and returns its - * `.binding` property, so you can just use the statement itself in your - * templates - * - * The one extra guarantee we need is that each import should import only - * **one** new binding. For example, `import { Button as VeniaButton } from - * '@magento/venia/lib/components/Button'` would be legal, because it adds - * exactly one binding in the document: "VeniaButton". Whereas `import { Button - * as VeniaButton, Carousel } from '@magento/venia'` would not be allowed, - * since it adds two bindings: "VeniaButton" and "Carousel". - * */ class SingleImportStatement { + /** + * @param {string} statement A static import statement + */ constructor(statement) { this.originalStatement = statement; this.statement = this._normalizeStatement(statement); @@ -77,31 +40,13 @@ class SingleImportStatement { this.imported = this._getImported(); // must come after this._getBinding } /** - * Return a new SingleImportStatement that is a copy of this one, but with - * the binding renamed. The `originalStatement` and `statement` properties - * are rewritten to use the new binding. - * - * @example - * - * ```js - * const useQueryImport = new SingleImportStatement("import { useQuery } from '@apollo/react-hooks'"); - * // SingleImportStatement { - * // statement: "import { useQuery } from '@apollo/react-hooks'", - * // binding: 'useQuery', - * // imported: 'useQuery' - * // } - * - * - * const useQueryImport2 = useQueryImport.changeBinding('useQuery2'); - * // SingleImportStatement { - * // statement: "import { useQuery as useQuery2 } from '@apollo/react-hooks'", - * // binding: 'useQuery2', - * // imported: 'useQuery' - * // } - * ``` + * Creates a new SingleImportStatement object with a different binding. * * @param {string} newBinding - Binding to rename. - * @returns SingleImportStatement + * + * @returns {SingleImportStatement} A new SingleImportStatement that is a copy + * of this one, but with the binding renamed. The `originalStatement` and + * `statement` properties are rewritten to use the new binding. */ changeBinding(newBinding) { const { imported, local } = this.node.specifiers[0]; @@ -129,18 +74,6 @@ class SingleImportStatement { * When interpolated as a string, a SingleImportStatement becomes the value * of its `binding` property. * - * @example Write JSX without knowing components' local names. - * - * ```js - * let Button = new SingleImportStatement("Button from './button'"); - * - * // later, we learn there is a conflict with the `Button` identifier - * Button = Button.changeBinding(generateUniqueIdentifier()); - * - * const jsx = `<${Button}>hello world` - * jsx === 'hello world'; - * ``` - * * @returns string */ toString() { diff --git a/packages/pwa-buildpack/lib/WebpackTools/targetables/TargetableESModule.js b/packages/pwa-buildpack/lib/WebpackTools/targetables/TargetableESModule.js index f014a9d7a6..d258b44563 100644 --- a/packages/pwa-buildpack/lib/WebpackTools/targetables/TargetableESModule.js +++ b/packages/pwa-buildpack/lib/WebpackTools/targetables/TargetableESModule.js @@ -4,7 +4,7 @@ const TargetableModule = require('./TargetableModule'); /** * An ECMAScript module that can be changed by a third party. * - * Presents a convenient API for consumers to add common transforms to ES + * This class presents a convenient API for consumers to add common transforms to ES * Modules in a semantic way. */ class TargetableESModule extends TargetableModule { @@ -14,13 +14,15 @@ class TargetableESModule extends TargetableModule { this._bindings = new Map(); } /** - * Add a static import statement to the module source code, thus importing + * Adds a static import statement to the module source code, thus importing * a new dependency. * - * Automatically deduplicates attempts to add imports that would override earler imports' bindings. If a collision is detected, it renames the binding before inserting it. + * This method automatically deduplicates attempts to add imports that would override + * earlier import bindings. + * If a collision is detected, it renames the binding before inserting it. * * @param {(string|SingleImportStatement)} statement - A string representing the import statement, or a SingleImportStatement representing it. - * @returns SingleImportStatement + * @returns {SingleImportStatement} An instance of the [`SingleImportStatement`] class. * @memberof TargetableESModule */ addImport(statement) { @@ -49,10 +51,11 @@ class TargetableESModule extends TargetableModule { return importStatement; } /** - * Generate a unique identifier for a given binding. Not guaranteed safe, but good enough in a pinch. + * Generates a unique identifier for a given binding. Not guaranteed safe, + * but good enough in a pinch. * * @param {string} binding - The binding to change. - * @returns string + * @returns {string} * @memberof TargetableESModule */ uniqueIdentifier(str) { @@ -60,10 +63,13 @@ class TargetableESModule extends TargetableModule { return `${str}$${TargetableESModule.increment}`; } /** - * Pass exports of this module through a [wrapper module](#wrapper_modules). + * Pass exports of this module through a wrapper module. * * @param {string} [exportName] Name of export to wrap. If not provided, will wrap the default export. - * @param {string} wrapperModule Import path to the wrapper module. Should be package-absolute. + * @param {string} wrapperModule Package-absolute import path to the wrapper module. + * + * @return { this } + * @chainable */ wrapWithFile(exportNameOrWrapperModule, wrapperModule) { const opts = wrapperModule diff --git a/packages/pwa-buildpack/lib/WebpackTools/targetables/TargetableESModuleArray.js b/packages/pwa-buildpack/lib/WebpackTools/targetables/TargetableESModuleArray.js index 7167bc3c64..45d983d72a 100644 --- a/packages/pwa-buildpack/lib/WebpackTools/targetables/TargetableESModuleArray.js +++ b/packages/pwa-buildpack/lib/WebpackTools/targetables/TargetableESModuleArray.js @@ -3,39 +3,9 @@ const TargetableESModule = require('./TargetableESModule'); /** * Builds a simple ES module that imports a list of other modules you provide, * and then re-exports those modules in order as an array. - * * Useful for building extensible navigation lists, routes, strategies, etc. * - * Uses [export-esm-collection-loader][] to build source code. - * - * @example Export PlainHtmlRenderer and PageBuilder in a list. - * - * ```js - * const renderers = targetable.esModuleArray('@magento/venia-ui/lib/components/RichContent/richContentRenderers.js'); - * - * renderers.push('import * as PageBuilder from "@magento/pagebuilder"'); - * renderers.push('import * as PlainHtmlRenderer from "@magento/venia-ui/lib/components/RichContent/plainHtmlRenderer"'); - * ``` - * - * The actual `richContentRenderers.js` file is a placeholder; it just exports an - * empty array `export default []`. - * - * After the transforms above, `richContentRenderers.js` will enter the bundle as: - * - * ```js - * import * as PageBuilder from '@magento/pagebuilder'; - * import * as PlainHtmlRenderer from '@magento/venia-ui/lib/components/RichContent/plainHtmlRenderer'; - * - * export default [ - * PageBuilder, - * PlainHtmlRenderer - * ]; - * ``` - * **Requires an "empty" module that exports either an empty array or an empty - * object. Without that module, the loader has nothing to "load" and will not - * execute. Also, without that module, code editors and linters may complain - * that it's missing.** - * + * This class uses [export-esm-collection-loader][] to build the source code. */ class TargetableESModuleArray extends TargetableESModule { constructor(...args) { @@ -43,22 +13,36 @@ class TargetableESModuleArray extends TargetableESModule { this._orderedBindings = []; } /** - * In this type of module, all imports must be exported, so this method - * becomes an alias to TargetableESModuleArray#push. + * Adds a module to the end of the array. + * + * This also acts as an alias for the `push()` function. + * + * @param {string} importString A static import declaration for a module * - * @alias TargetableESModuleArray#push + * @returns {undefined} */ addImport(importString) { return this.push(importString); } - /** @alias TargetableESModuleArray#push */ + + /** + * Add a module or modules to the end of the array. + * + * This also acts as an alias for the `push()` function. + * + * @param {...any} items Static import declaration(s) + * + * @returns {undefined} + */ add(...items) { return this.push(...items); } /** * Add a module or modules to the end of the array. + * * @param {...string} importString - Static import declaration(s) - * @returns undefined + * + * @returns {undefined} */ push(...items) { return this._forEachBinding(items, item => @@ -67,8 +51,10 @@ class TargetableESModuleArray extends TargetableESModule { } /** * Add a module or modules to the _beginning_ of the array. + * * @param {...string} importString - Static import declaration(s) - * @returns undefined + * + * @returns {undefined} */ unshift(...items) { return this._forEachBinding(items, item => diff --git a/packages/pwa-buildpack/lib/WebpackTools/targetables/TargetableESModuleObject.js b/packages/pwa-buildpack/lib/WebpackTools/targetables/TargetableESModuleObject.js index 316f3d8a4b..8fefc89fbc 100644 --- a/packages/pwa-buildpack/lib/WebpackTools/targetables/TargetableESModuleObject.js +++ b/packages/pwa-buildpack/lib/WebpackTools/targetables/TargetableESModuleObject.js @@ -5,39 +5,9 @@ const TargetableESModule = require('./TargetableESModule'); * Builds a simple ES module that imports a list of other modules you provide, * and then re-exports those modules as an object with properties matching the * imported bindings. - * * Useful for building named lists and associative arrays when making extension points. * * Uses [export-esm-collection-loader][] to build source code. - * - * @example Export three styles of button in a mapping. - * - * ```js - * const buttons = targetable.esModuleArray('path/to/buttons.js'); - * - * buttons.add("import Primary from './path/to/Primary'"); - * buttons.add("import { Button as Simple } from './path/to/simple'"); - * buttons.add("import Secondary from './path/to/Standard'"); - * ``` - * - * The actual `path/to/buttons.js` file is a placeholder; it just exports an - * empty object `export default {}`. - * - * After the transforms above, `./path/to/button.js` will enter the bundle as: - * - * ```js - * import Primary from './path/to/Primary'"); - * import { Button as Simple } from './path/to/simple'"); - * import { Secondary } from './path/to/Standard'"); - * - * export default { Primary, Simple, Secondary }; - * ``` - * - * **Requires an "empty" module that exports either an empty array or an empty - * object. Without that module, the loader has nothing to "load" and will not - * execute. Also, without that module, code editors and linters may complain - * that it's missing.** - * */ class TargetableESModuleObject extends TargetableESModule { constructor(...args) { @@ -61,10 +31,13 @@ class TargetableESModuleObject extends TargetableESModule { return super.flush().reverse(); } /** - * In this type of module, all imports must be exported, so this method - * gains some additional validation and behavior. + * Adds a module to the object using the `addImport()` method from TargetableESModule. + * Since, all imports must be exported, this method performs additional validation. * - * @alias TargetableESModuleObject#add + * @param {string} importString A static import declaration + * + * @return { this } + * @chainable */ addImport(importString) { const importStatement = new SingleImportStatement(importString); @@ -84,6 +57,15 @@ class TargetableESModuleObject extends TargetableESModule { } return this; } + + /** + * Adds a module or modules to the object using the `addImport()` function. + * + * @param {...string} args Static import declaration(s) + * + * @return { this } + * @chainable + */ add(...args) { args.forEach(arg => this.addImport(arg)); return this; diff --git a/packages/pwa-buildpack/lib/WebpackTools/targetables/TargetableModule.js b/packages/pwa-buildpack/lib/WebpackTools/targetables/TargetableModule.js index 6ce8aed96d..c654ee7156 100644 --- a/packages/pwa-buildpack/lib/WebpackTools/targetables/TargetableModule.js +++ b/packages/pwa-buildpack/lib/WebpackTools/targetables/TargetableModule.js @@ -1,19 +1,18 @@ const Trackable = require('../../BuildBus/Trackable'); /** - * A module that can be changed by a third party. + * A module that third party code can modify. * * When Webpack loads a module into its bundles, it processes the source code - * through a set of rules generated by Buildpack. A PublicModule is a reference + * through a set of rules generated by Buildpack. A TargetableModule is a reference * to that source file, meant to be passed to interceptors. Inside - * interceptors, extensions and projects can configure the PublicModule to - * transform in many ways. + * interceptors, extensions and projects can configure the TargetableModule to + * transform it in many ways. */ class TargetableModule extends Trackable { /** * Create a TargetableModule representing a file. * @param {string} file - Path to the underlying source file. * @param {Trackable} trackingOwner - Parent object for debugging purposes. - * @memberof TargetableModule */ constructor(file, trackingOwner) { super(); @@ -25,9 +24,11 @@ class TargetableModule extends Trackable { * Add a transform request to this module's queue. The `fileToTransform` of * the transform request is automatically set to this module's filename. * - * @param {TransformType} type - Transform type, see ModuleTransformConfig + * @param {TransformType} type - [Transform type][] * @param {string} transformModule - The Node module that runs the transform, such as a Webpack loader for type `source` or a Babel plugin for type `babel`. * @param {Object} options - Configuration object to send to the transformModule. + * + * @return { this } * @chainable */ addTransform(type, transformModule, options) { @@ -39,7 +40,7 @@ class TargetableModule extends Trackable { /** * Empty this module's queue of transforms, returning them as an array. * - * @returns TransformRequest[] + * @returns {TransformRequest[]} An array of [Transform requests][]. */ flush() { return this._queuedTransforms.splice(0, this._queuedTransforms.length); @@ -52,6 +53,8 @@ class TargetableModule extends Trackable { * @param {string} insert - Text to insert after the search string. * @param {Object} [options] - Additional loader options. * @param {number} [options.remove] - Number of characters to delete forward, after the search string. + * + * @return { this } * @chainable */ insertAfterSource(after, insert, options = {}) { @@ -69,6 +72,8 @@ class TargetableModule extends Trackable { * @param {string} insert - Text to insert before the search string. * @param {Object} [options] - Additional loader options. * @param {number} [options.remove] - Number of characters to delete forward, after the search string. + * + * @return { this } * @chainable */ insertBeforeSource(before, insert, options = {}) { @@ -82,16 +87,19 @@ class TargetableModule extends Trackable { * Add text to the beginning of a file. * * @param {string} insert - Text to insert up top + * + * @return { this } * @chainable */ prependSource(insert) { return this.spliceSource({ at: 0, insert }); } /** - * Do any splice operation supported by `splice-source-loader`. - * @see splice-source-loader + * Do any splice operation supported by [`splice-source-loader`][]. * * @param {object} instruction - Splice instruction. + * + * @return { this } * @chainable * @memberof TargetableModule */ diff --git a/packages/pwa-buildpack/lib/WebpackTools/targetables/TargetableReactComponent.js b/packages/pwa-buildpack/lib/WebpackTools/targetables/TargetableReactComponent.js index 337aeb096d..e5b835c2a6 100644 --- a/packages/pwa-buildpack/lib/WebpackTools/targetables/TargetableReactComponent.js +++ b/packages/pwa-buildpack/lib/WebpackTools/targetables/TargetableReactComponent.js @@ -27,6 +27,8 @@ class TargetableReactComponent extends TargetableESModule { * variable `classname` in the file. If that identifier doesn't exist, * it'll cause a ReferenceError. * @param {JSXModifierOptions} [options] + * + * @return { this } * @chainable */ addJSXClassName(element, className, options) { @@ -68,6 +70,8 @@ class TargetableReactComponent extends TargetableESModule { * @param {string} element - Match an existing JSX component in the file * @param {string} newChild - New element to insert, as a string. * @param {JSXModifierOptions} [options] + * + * @return { this } * @chainable */ appendJSX(element, newChild, options) { @@ -79,6 +83,8 @@ class TargetableReactComponent extends TargetableESModule { * @param {string} element - Match an existing JSX component in the file * @param {string} newSibling - New element to insert, as a string. * @param {JSXModifierOptions} [options] + * + * @return { this } * @chainable */ insertAfterJSX(element, sibling, options) { @@ -90,6 +96,8 @@ class TargetableReactComponent extends TargetableESModule { * @param {string} element - Match an existing JSX component in the file * @param {string} newSibling - New element to insert, as a string. * @param {JSXModifierOptions} [options] + * + * @return { this } * @chainable */ insertBeforeJSX(element, sibling, options) { @@ -101,6 +109,8 @@ class TargetableReactComponent extends TargetableESModule { * @param {string} element - Match an existing JSX component in the file * @param {string} newChild - New element to insert, as a string. * @param {JSXModifierOptions} [options] + * + * @return { this } * @chainable */ prependJSX(element, child, options) { @@ -112,6 +122,8 @@ class TargetableReactComponent extends TargetableESModule { * * @param {string} element - Match an existing JSX component in the file and remove it * @param {JSXModifierOptions} [options] + * + * @return { this } * @chainable */ removeJSX(element, options) { @@ -124,6 +136,8 @@ class TargetableReactComponent extends TargetableESModule { * @param {string} element - Match an existing JSX component in the file. * @param {string[]} propNames - An array of names of props to remove. * @param {JSXModifierOptions} [options] + * + * @return { this } * @chainable */ removeJSXProps(element, props, options) { @@ -138,6 +152,9 @@ class TargetableReactComponent extends TargetableESModule { * ''. * @param {string} replacement - Replacement code as a string. * @param {JSXModifierOptions} [options] + * + * @return { this } + * @chainable */ replaceJSX(element, replacement, options) { return this._addJsxTransform('replace', element, replacement, options); @@ -158,6 +175,8 @@ class TargetableReactComponent extends TargetableESModule { * className: '{classes.tabs}' * }) * ``` + * + * @return { this } * @chainable */ setJSXProps(element, props, options) { @@ -171,8 +190,10 @@ class TargetableReactComponent extends TargetableESModule { * @param {string} element - Match an existing JSX component in the file. * @param {string} newParent - The wrapper element as a JSX string. It must be one and only one JSX element with no children; the matching element will be the only child of the new wrapper. * @param {JSXModifierOptions} [options] - * @returns * @memberof TargetableReactComponent + * + * @return { this } + * @chainable */ surroundJSX(element, newParent, options) { return this._addJsxTransform('surround', element, newParent, options); diff --git a/packages/pwa-buildpack/lib/WebpackTools/targetables/TargetableSet.js b/packages/pwa-buildpack/lib/WebpackTools/targetables/TargetableSet.js index cddecd8237..cc00f6b0e3 100644 --- a/packages/pwa-buildpack/lib/WebpackTools/targetables/TargetableSet.js +++ b/packages/pwa-buildpack/lib/WebpackTools/targetables/TargetableSet.js @@ -8,16 +8,16 @@ const types = { const TargetProvider = require('../../BuildBus/TargetProvider'); /** - * A factory and manager for Targetable instances. The usual way to use - * Targetables. Wraps around a TargetProvider, which identifies it as "your" + * A factory and manager for Targetable instances. + * This class wraps around a TargetProvider, which identifies it as "your" * Targetable and enables automatic interception of targets. */ class TargetableSet { /** - * Create a new TargetableSet bound to a TargetProvider. + * Creates a new TargetableSet bound to a TargetProvider * * @param {TargetProvider} targets - TargetProvider for the curent dependency. This is the object passed by BuildBus to an intercept function. - * @returns TargetableSet + * @returns {TargetableSet} */ static using(targets) { return new TargetableSet(targets); @@ -36,38 +36,64 @@ class TargetableSet { this._bind(); } + /** - * @param {string} modulePath - Path to the module file this Targetable represents. Can be either module-resolvable (e.g. `"@magento/venia-ui/lib/components/Button"`), or module-root-relative (e.g. `"lib/components/Button"`), in which case the module name `@magento/venia-ui` will be added automatically. - * @param {TargetablePublisher} [publisher] - Callback function to execute when this module is about to commit its requested transforms to a build. If this function is passed, the module will automatically bind to `builtins.transformModules`. - * @returns {TargetableModule} - */ - /** - * @param {object} config - Setup config for new TargetableModule. - * @param {string} config.module - Path to the module file this Targetable represents. Can be either module-resolvable (e.g. `"@magento/venia-ui/lib/components/Button"`), or module-root-relative (e.g. `"lib/components/Button"`), in which case the module name `@magento/venia-ui` will be added automatically. - * @param {TargetablePublisher} config.publish - Callback function to execute when this module is about to commit its requested transforms to a build. If this function is passed, the module will automatically bind to `builtins.transformModules`. - * @returns {TargetableModule} + * @param {string} modulePath - Path to the module file this Targetable represents. + * @param {TargetablePublisher} [publisher] - Callback function to execute when this module + * is about to commit its requested transforms to a build. If this function is passed, + * the module will automatically bind to `builtins.transformModules`. + * @returns {TargetableModule} Returns an instance of [TargetableModule][] */ module(modulePath, publisher) { return this._provide(types.Module, modulePath, publisher); } - /** Like Targetable#module, but creates a TargetableESModule. */ + + /** + * @param {string} modulePath - Path to the module file this Targetable represents. + * @param {TargetablePublisher} [publisher] - Callback function to execute when this module + * is about to commit its requested transforms to a build. If this function is passed, + * the module will automatically bind to `builtins.transformModules`. + * @returns {TargetableESModule} Returns an instance of [TargetableESModule][] + */ esModule(modulePath, publisher) { return this._provide(types.ESModule, modulePath, publisher); } - /** Like Targetable#module, but creates a TargetableESModuleArray. */ + + /** + * @param {string} modulePath - Path to the module file this Targetable represents. + * @param {TargetablePublisher} [publisher] - Callback function to execute when this module + * is about to commit its requested transforms to a build. If this function is passed, + * the module will automatically bind to `builtins.transformModules`. + * @returns {TargetableESModuleArray} Returns an instance of [TargetableESModuleArray][] + */ esModuleArray(modulePath, publisher) { return this._provide(types.ESModuleArray, modulePath, publisher); } - /** Like Targetable#module, but creates a TargetableESModuleObject. */ + + /** + * @param {string} modulePath - Path to the module file this Targetable represents. + * @param {TargetablePublisher} [publisher] - Callback function to execute when this module + * is about to commit its requested transforms to a build. If this function is passed, + * the module will automatically bind to `builtins.transformModules`. + * @returns {TargetableESModuleObject} Returns an instance of [TargetableESModuleObject][] + */ esModuleObject(modulePath, publisher) { return this._provide(types.ESModuleObject, modulePath, publisher); } - /** Like Targetable#module, but creates a TargetableReactComponent. */ + + /** + * @param {string} modulePath - Path to the module file this Targetable represents. + * @param {TargetablePublisher} [publisher] - Callback function to execute when this module + * is about to commit its requested transforms to a build. If this function is passed, + * the module will automatically bind to `builtins.transformModules`. + * @returns {TargetableReactComponent} Returns an instance of [TargetableReactComponent][] + */ reactComponent(modulePath, publisher) { return this._provide(types.ReactComponent, modulePath, publisher); } + /** - * Tap the builtin `specialFeatures` target and set the supplied feature flags. + * Taps the builtin `specialFeatures` target and sets the supplied feature flags. * * @param {...(string|string[]|object)} Feature flags to set, as either string arguments, an array of string arguments, or an object of flags. */ diff --git a/packages/venia-ui/lib/components/AccountInformationPage/editModal.js b/packages/venia-ui/lib/components/AccountInformationPage/editModal.js index 4c884bc313..b2a18cc78a 100644 --- a/packages/venia-ui/lib/components/AccountInformationPage/editModal.js +++ b/packages/venia-ui/lib/components/AccountInformationPage/editModal.js @@ -36,6 +36,7 @@ const EditModal = props => { onConfirm={onSubmit} shouldDisableAllButtons={isDisabled} shouldDisableConfirmButton={isDisabled} + shouldUnmountOnHide={true} title={formatMessage({ id: 'accountInformationPage.editAccount', defaultMessage: 'Edit Account Information' diff --git a/packages/venia-ui/lib/components/AddressBookPage/__tests__/__snapshots__/addEditDialog.spec.js.snap b/packages/venia-ui/lib/components/AddressBookPage/__tests__/__snapshots__/addEditDialog.spec.js.snap index 6eb303a06e..723c92733e 100644 --- a/packages/venia-ui/lib/components/AddressBookPage/__tests__/__snapshots__/addEditDialog.spec.js.snap +++ b/packages/venia-ui/lib/components/AddressBookPage/__tests__/__snapshots__/addEditDialog.spec.js.snap @@ -8,7 +8,6 @@ exports[`Edit Mode renders correctly 1`] = ` onCancel={[MockFunction onCancel]} onConfirm={[MockFunction onConfirm]} shouldDisableAllButtons={false} - shouldUnmountOnHide={true} title="Edit Address" > { onCancel={onCancel} onConfirm={onConfirm} shouldDisableAllButtons={isBusy} - shouldUnmountOnHide={true} title={title} >