From f2a0b6c2bf8978b37a4c4c33c27c0234088bae7c Mon Sep 17 00:00:00 2001 From: Jarda Snajdr Date: Wed, 22 Sep 2021 10:04:38 +0200 Subject: [PATCH] block-directory: migrate store to thunks --- package-lock.json | 1 - packages/block-directory/package.json | 1 - packages/block-directory/src/store/actions.js | 82 ++- packages/block-directory/src/store/index.js | 6 +- .../src/store/{controls.js => load-assets.js} | 64 +-- .../block-directory/src/store/resolvers.js | 36 +- .../block-directory/src/store/test/actions.js | 493 +++++++++--------- .../test/{controls.js => load-assets.js} | 2 +- 8 files changed, 314 insertions(+), 371 deletions(-) rename packages/block-directory/src/store/{controls.js => load-assets.js} (54%) rename packages/block-directory/src/store/test/{controls.js => load-assets.js} (92%) diff --git a/package-lock.json b/package-lock.json index 207b2fc63f128..283d87eccae51 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18041,7 +18041,6 @@ "@wordpress/compose": "file:packages/compose", "@wordpress/core-data": "file:packages/core-data", "@wordpress/data": "file:packages/data", - "@wordpress/data-controls": "file:packages/data-controls", "@wordpress/edit-post": "file:packages/edit-post", "@wordpress/editor": "file:packages/editor", "@wordpress/element": "file:packages/element", diff --git a/packages/block-directory/package.json b/packages/block-directory/package.json index e8e256ed95cb7..ab7013a312e76 100644 --- a/packages/block-directory/package.json +++ b/packages/block-directory/package.json @@ -34,7 +34,6 @@ "@wordpress/compose": "file:../compose", "@wordpress/core-data": "file:../core-data", "@wordpress/data": "file:../data", - "@wordpress/data-controls": "file:../data-controls", "@wordpress/edit-post": "file:../edit-post", "@wordpress/editor": "file:../editor", "@wordpress/element": "file:../element", diff --git a/packages/block-directory/src/store/actions.js b/packages/block-directory/src/store/actions.js index 3a69b9f892d07..2ab21b6cf9fdd 100644 --- a/packages/block-directory/src/store/actions.js +++ b/packages/block-directory/src/store/actions.js @@ -3,14 +3,13 @@ */ import { store as blocksStore } from '@wordpress/blocks'; import { __, sprintf } from '@wordpress/i18n'; -import { controls } from '@wordpress/data'; -import { apiFetch } from '@wordpress/data-controls'; +import apiFetch from '@wordpress/api-fetch'; import { store as noticesStore } from '@wordpress/notices'; /** * Internal dependencies */ -import { loadAssets } from './controls'; +import { loadAssets } from './load-assets'; import getPluginUrl from './utils/get-plugin-url'; /** @@ -49,56 +48,49 @@ export function receiveDownloadableBlocks( downloadableBlocks, filterValue ) { * * @return {boolean} Whether the block was successfully installed & loaded. */ -export function* installBlockType( block ) { +export const installBlockType = ( block ) => async ( { + registry, + dispatch, +} ) => { const { id } = block; let success = false; - yield clearErrorNotice( id ); + dispatch.clearErrorNotice( id ); try { - yield setIsInstalling( id, true ); + dispatch.setIsInstalling( id, true ); // If we have a wp:plugin link, the plugin is installed but inactive. const url = getPluginUrl( block ); let links = {}; if ( url ) { - yield apiFetch( { - url, - data: { - status: 'active', - }, + await apiFetch( { method: 'PUT', + url, + data: { status: 'active' }, } ); } else { - const response = yield apiFetch( { - path: 'wp/v2/plugins', - data: { - slug: id, - status: 'active', - }, + const response = await apiFetch( { method: 'POST', + path: 'wp/v2/plugins', + data: { slug: id, status: 'active' }, } ); // Add the `self` link for newly-installed blocks. links = response._links; } - yield addInstalledBlockType( { + dispatch.addInstalledBlockType( { ...block, links: { ...block.links, ...links }, } ); - yield loadAssets(); - const registeredBlocks = yield controls.select( - blocksStore, - 'getBlockTypes' - ); + await loadAssets(); + const registeredBlocks = registry.select( blocksStore ).getBlockTypes(); if ( ! registeredBlocks.some( ( i ) => i.name === block.name ) ) { throw new Error( __( 'Error registering block. Try reloading the page.' ) ); } - yield controls.dispatch( - noticesStore, - 'createInfoNotice', + registry.dispatch( noticesStore ).createInfoNotice( sprintf( // translators: %s is the block title. __( 'Block %s installed and added.' ), @@ -131,43 +123,43 @@ export function* installBlockType( block ) { message = fatalAPIErrors[ error.code ]; } - yield setErrorNotice( id, message, isFatal ); - yield controls.dispatch( noticesStore, 'createErrorNotice', message, { + dispatch.setErrorNotice( id, message, isFatal ); + registry.dispatch( noticesStore ).createErrorNotice( message, { speak: true, isDismissible: true, } ); } - yield setIsInstalling( id, false ); + dispatch.setIsInstalling( id, false ); return success; -} +}; /** * Action triggered to uninstall a block plugin. * * @param {Object} block The blockType object. */ -export function* uninstallBlockType( block ) { +export const uninstallBlockType = ( block ) => async ( { + registry, + dispatch, +} ) => { try { - yield apiFetch( { - url: getPluginUrl( block ), - data: { - status: 'inactive', - }, + const url = getPluginUrl( block ); + await apiFetch( { method: 'PUT', + url, + data: { status: 'inactive' }, } ); - yield apiFetch( { - url: getPluginUrl( block ), + await apiFetch( { method: 'DELETE', + url, } ); - yield removeInstalledBlockType( block ); + dispatch.removeInstalledBlockType( block ); } catch ( error ) { - yield controls.dispatch( - noticesStore, - 'createErrorNotice', - error.message || __( 'An error occurred.' ) - ); + registry + .dispatch( noticesStore ) + .createErrorNotice( error.message || __( 'An error occurred.' ) ); } -} +}; /** * Returns an action object used to add a block type to the "newly installed" diff --git a/packages/block-directory/src/store/index.js b/packages/block-directory/src/store/index.js index 89f64e4485a49..dd2f7837ec06f 100644 --- a/packages/block-directory/src/store/index.js +++ b/packages/block-directory/src/store/index.js @@ -2,7 +2,6 @@ * WordPress dependencies */ import { createReduxStore, register } from '@wordpress/data'; -import { controls as dataControls } from '@wordpress/data-controls'; /** * Internal dependencies @@ -10,8 +9,7 @@ import { controls as dataControls } from '@wordpress/data-controls'; import reducer from './reducer'; import * as selectors from './selectors'; import * as actions from './actions'; -import resolvers from './resolvers'; -import controls from './controls'; +import * as resolvers from './resolvers'; /** * Module Constants @@ -29,8 +27,8 @@ export const storeConfig = { reducer, selectors, actions, - controls: { ...dataControls, ...controls }, resolvers, + __experimentalUseThunks: true, }; /** diff --git a/packages/block-directory/src/store/controls.js b/packages/block-directory/src/store/load-assets.js similarity index 54% rename from packages/block-directory/src/store/controls.js rename to packages/block-directory/src/store/load-assets.js index e54e518c6c137..96cda94911de5 100644 --- a/packages/block-directory/src/store/controls.js +++ b/packages/block-directory/src/store/load-assets.js @@ -49,47 +49,33 @@ export const loadAsset = ( el ) => { /** * Load the asset files for a block - * - * @return {Object} Control descriptor. */ -export function loadAssets() { - return { - type: 'LOAD_ASSETS', - }; -} - -const controls = { - async LOAD_ASSETS() { - /* - * Fetch the current URL (post-new.php, or post.php?post=1&action=edit) and compare the - * JavaScript and CSS assets loaded between the pages. This imports the required assets - * for the block into the current page while not requiring that we know them up-front. - * In the future this can be improved by reliance upon block.json and/or a script-loader - * dependency API. - */ - const response = await apiFetch( { - url: document.location.href, - parse: false, - } ); - - const data = await response.text(); +export async function loadAssets() { + /* + * Fetch the current URL (post-new.php, or post.php?post=1&action=edit) and compare the + * JavaScript and CSS assets loaded between the pages. This imports the required assets + * for the block into the current page while not requiring that we know them up-front. + * In the future this can be improved by reliance upon block.json and/or a script-loader + * dependency API. + */ + const response = await apiFetch( { + url: document.location.href, + parse: false, + } ); - const doc = new window.DOMParser().parseFromString( data, 'text/html' ); + const data = await response.text(); - const newAssets = Array.from( - doc.querySelectorAll( 'link[rel="stylesheet"],script' ) - ).filter( - ( asset ) => asset.id && ! document.getElementById( asset.id ) - ); + const doc = new window.DOMParser().parseFromString( data, 'text/html' ); - /* - * Load each asset in order, as they may depend upon an earlier loaded script. - * Stylesheets and Inline Scripts will resolve immediately upon insertion. - */ - for ( const newAsset of newAssets ) { - await loadAsset( newAsset ); - } - }, -}; + const newAssets = Array.from( + doc.querySelectorAll( 'link[rel="stylesheet"],script' ) + ).filter( ( asset ) => asset.id && ! document.getElementById( asset.id ) ); -export default controls; + /* + * Load each asset in order, as they may depend upon an earlier loaded script. + * Stylesheets and Inline Scripts will resolve immediately upon insertion. + */ + for ( const newAsset of newAssets ) { + await loadAsset( newAsset ); + } +} diff --git a/packages/block-directory/src/store/resolvers.js b/packages/block-directory/src/store/resolvers.js index 98c7ae64bb776..5cd69cf584ef3 100644 --- a/packages/block-directory/src/store/resolvers.js +++ b/packages/block-directory/src/store/resolvers.js @@ -6,31 +6,29 @@ import { camelCase, mapKeys } from 'lodash'; /** * WordPress dependencies */ -import { apiFetch } from '@wordpress/data-controls'; +import apiFetch from '@wordpress/api-fetch'; /** * Internal dependencies */ import { fetchDownloadableBlocks, receiveDownloadableBlocks } from './actions'; -export default { - *getDownloadableBlocks( filterValue ) { - if ( ! filterValue ) { - return; - } +export const getDownloadableBlocks = ( filterValue ) => async ( { + dispatch, +} ) => { + if ( ! filterValue ) { + return; + } - try { - yield fetchDownloadableBlocks( filterValue ); - const results = yield apiFetch( { - path: `wp/v2/block-directory/search?term=${ filterValue }`, - } ); - const blocks = results.map( ( result ) => - mapKeys( result, ( value, key ) => { - return camelCase( key ); - } ) - ); + try { + dispatch( fetchDownloadableBlocks( filterValue ) ); + const results = await apiFetch( { + path: `wp/v2/block-directory/search?term=${ filterValue }`, + } ); + const blocks = results.map( ( result ) => + mapKeys( result, ( value, key ) => camelCase( key ) ) + ); - yield receiveDownloadableBlocks( blocks, filterValue ); - } catch ( error ) {} - }, + dispatch( receiveDownloadableBlocks( blocks, filterValue ) ); + } catch {} }; diff --git a/packages/block-directory/src/store/test/actions.js b/packages/block-directory/src/store/test/actions.js index 505c013a35777..5520f2ac1149d 100644 --- a/packages/block-directory/src/store/test/actions.js +++ b/packages/block-directory/src/store/test/actions.js @@ -1,18 +1,57 @@ /** * WordPress dependencies */ +import { createRegistry } from '@wordpress/data'; import { store as blocksStore } from '@wordpress/blocks'; import { store as noticesStore } from '@wordpress/notices'; +import apiFetch from '@wordpress/api-fetch'; /** * Internal dependencies */ -import { installBlockType, uninstallBlockType } from '../actions'; +import { loadAssets } from '../load-assets'; +import { store as blockDirectoryStore } from '..'; + +jest.mock( '@wordpress/api-fetch', () => ( { + __esModule: true, + default: jest.fn(), +} ) ); + +jest.mock( '../load-assets', () => ( { + loadAssets: jest.fn(), +} ) ); + +function createRegistryWithStores() { + // create a registry and register stores + const registry = createRegistry(); + + registry.register( blockDirectoryStore ); + registry.register( noticesStore ); + registry.register( blocksStore ); + + return registry; +} + +// mock the `loadAssets` function. The real function would load the installed +// block's script assets, which in turn register the block. That registration +// call is the only thing we need to mock. +function loadAssetsMock( registry ) { + return async function () { + registry + .dispatch( blocksStore ) + .addBlockTypes( [ { name: 'block/block' } ] ); + }; +} + +function blockWithLinks( block, links ) { + return { ...block, links: { ...block.links, ...links } }; +} describe( 'actions', () => { const pluginEndpoint = 'https://example.com/wp-json/wp/v2/plugins/block-block'; - const item = { + + const block = { id: 'block-block', name: 'block/block', title: 'Test Block', @@ -25,286 +64,218 @@ describe( 'actions', () => { ], }, }; - const plugin = { + + const pluginResponse = { plugin: 'block/block.php', status: 'active', name: 'Test Block', version: '1.0.0', _links: { - self: [ - { - href: pluginEndpoint, - }, - ], + self: [ { href: pluginEndpoint } ], }, }; describe( 'installBlockType', () => { - const block = item; - it( 'should install a block successfully', () => { - const generator = installBlockType( block ); - - expect( generator.next().value ).toEqual( { - type: 'CLEAR_ERROR_NOTICE', - blockId: block.id, - } ); - - expect( generator.next().value ).toEqual( { - type: 'SET_INSTALLING_BLOCK', - blockId: block.id, - isInstalling: true, - } ); - - expect( generator.next().value ).toMatchObject( { - type: 'API_FETCH', - request: { - path: 'wp/v2/plugins', - method: 'POST', - }, - } ); - - expect( generator.next( plugin ).value ).toEqual( { - type: 'ADD_INSTALLED_BLOCK_TYPE', - item: { - ...block, - links: { - ...block.links, - self: [ - { - href: pluginEndpoint, - }, - ], - }, - }, - } ); - - expect( generator.next().value ).toEqual( { - type: 'LOAD_ASSETS', - } ); - - expect( generator.next().value ).toEqual( { - args: [], - selectorName: 'getBlockTypes', - storeKey: blocksStore.name, - type: '@@data/SELECT', - } ); - - expect( generator.next( [ block ] ).value ).toMatchObject( { - type: '@@data/DISPATCH', - actionName: 'createInfoNotice', - storeKey: noticesStore.name, - } ); - - expect( generator.next().value ).toEqual( { - type: 'SET_INSTALLING_BLOCK', - blockId: block.id, - isInstalling: false, - } ); - - expect( generator.next() ).toEqual( { - value: true, - done: true, - } ); + it( 'should install a block successfully', async () => { + const registry = createRegistryWithStores(); + + // mock the api-fetch and load-assets modules + apiFetch.mockImplementation( async ( { path } ) => { + switch ( path ) { + case 'wp/v2/plugins': + return pluginResponse; + default: + throw new Error( `unexpected API endpoint: ${ path }` ); + } + } ); + + loadAssets.mockImplementation( loadAssetsMock( registry ) ); + + // install the block + await registry + .dispatch( blockDirectoryStore ) + .installBlockType( block ); + + // check that blocks store contains the new block + const registeredBlock = registry + .select( blocksStore ) + .getBlockType( 'block/block' ); + expect( registeredBlock ).toBeTruthy(); + + // check that the block-directory store contains the new block, too + const installedBlockTypes = registry + .select( blockDirectoryStore ) + .getInstalledBlockTypes(); + expect( installedBlockTypes ).toMatchObject( [ + { name: 'block/block' }, + ] ); + + // check that notice was displayed + const notices = registry.select( noticesStore ).getNotices(); + expect( notices ).toMatchObject( [ + { content: 'Block Test Block installed and added.' }, + ] ); } ); - it( 'should activate an inactive block plugin successfully', () => { - const inactiveBlock = { - ...block, - links: { - ...block.links, - 'wp:plugin': [ - { - href: pluginEndpoint, - }, - ], - }, - }; - const generator = installBlockType( inactiveBlock ); - - expect( generator.next().value ).toEqual( { - type: 'CLEAR_ERROR_NOTICE', - blockId: inactiveBlock.id, - } ); - - expect( generator.next().value ).toEqual( { - type: 'SET_INSTALLING_BLOCK', - blockId: inactiveBlock.id, - isInstalling: true, - } ); - - expect( generator.next().value ).toMatchObject( { - type: 'API_FETCH', - request: { - url: pluginEndpoint, - method: 'PUT', - }, - } ); - - expect( generator.next( plugin ).value ).toEqual( { - type: 'ADD_INSTALLED_BLOCK_TYPE', - item: inactiveBlock, - } ); - - expect( generator.next().value ).toEqual( { - type: 'LOAD_ASSETS', - } ); - - expect( generator.next().value ).toEqual( { - args: [], - selectorName: 'getBlockTypes', - storeKey: blocksStore.name, - type: '@@data/SELECT', - } ); - - expect( generator.next( [ inactiveBlock ] ).value ).toMatchObject( { - type: '@@data/DISPATCH', - actionName: 'createInfoNotice', - storeKey: noticesStore.name, - } ); - - expect( generator.next().value ).toEqual( { - type: 'SET_INSTALLING_BLOCK', - blockId: inactiveBlock.id, - isInstalling: false, - } ); - - expect( generator.next() ).toEqual( { - value: true, - done: true, - } ); + it( 'should activate an inactive block plugin successfully', async () => { + const registry = createRegistryWithStores(); + + // mock the api-fetch and load-assets modules + apiFetch.mockImplementation( async ( p ) => { + const { url } = p; + switch ( url ) { + case pluginEndpoint: + return pluginResponse; + default: + throw new Error( `unexpected API endpoint: ${ url }` ); + } + } ); + + loadAssets.mockImplementation( loadAssetsMock( registry ) ); + + // install the block + await registry.dispatch( blockDirectoryStore ).installBlockType( + blockWithLinks( block, { + 'wp:plugin': [ { href: pluginEndpoint } ], + } ) + ); + + // check that blocks store contains the new block + const registeredBlock = registry + .select( blocksStore ) + .getBlockType( 'block/block' ); + expect( registeredBlock ).toBeTruthy(); + + // check that notice was displayed + const notices = registry.select( noticesStore ).getNotices(); + expect( notices ).toMatchObject( [ + { content: 'Block Test Block installed and added.' }, + ] ); } ); - it( "should set an error if the plugin can't install", () => { - const generator = installBlockType( block ); - - expect( generator.next().value ).toEqual( { - type: 'CLEAR_ERROR_NOTICE', - blockId: block.id, - } ); - - expect( generator.next().value ).toEqual( { - type: 'SET_INSTALLING_BLOCK', - blockId: block.id, - isInstalling: true, - } ); - - expect( generator.next().value ).toMatchObject( { - type: 'API_FETCH', - request: { - path: 'wp/v2/plugins', - method: 'POST', - }, - } ); - - const apiError = { - code: 'plugins_api_failed', - message: 'Plugin not found.', - data: null, - }; - expect( generator.throw( apiError ).value ).toMatchObject( { - type: 'SET_ERROR_NOTICE', - blockId: block.id, - } ); - - expect( generator.next().value ).toMatchObject( { - type: '@@data/DISPATCH', - actionName: 'createErrorNotice', - storeKey: noticesStore.name, - } ); - - expect( generator.next().value ).toEqual( { - type: 'SET_INSTALLING_BLOCK', - blockId: block.id, - isInstalling: false, - } ); - - expect( generator.next() ).toEqual( { - value: false, - done: true, - } ); + it( "should set an error if the plugin can't install", async () => { + const registry = createRegistryWithStores(); + + // mock the api-fetch and load-assets modules + apiFetch.mockImplementation( async ( { path } ) => { + switch ( path ) { + case 'wp/v2/plugins': + throw { + code: 'plugins_api_failed', + message: 'Plugin not found.', + data: null, + }; + default: + throw new Error( `unexpected API endpoint: ${ path }` ); + } + } ); + + loadAssets.mockImplementation( loadAssetsMock( registry ) ); + + // install the block + await registry + .dispatch( blockDirectoryStore ) + .installBlockType( block ); + + // check that blocks store doesn't contain the new block + const registeredBlock = registry + .select( blocksStore ) + .getBlockType( 'block/block' ); + expect( registeredBlock ).toBeUndefined(); + + // check that error notice was displayed + const notices = registry.select( noticesStore ).getNotices(); + expect( notices ).toMatchObject( [ + { content: 'Plugin not found.' }, + ] ); } ); } ); describe( 'uninstallBlockType', () => { - const block = { - ...item, - links: { - ...item.links, - self: [ - { - href: pluginEndpoint, - }, - ], - }, - }; - - it( 'should uninstall a block successfully', () => { - const generator = uninstallBlockType( block ); - - // First the deactivation step - expect( generator.next().value ).toMatchObject( { - type: 'API_FETCH', - request: { - url: pluginEndpoint, - method: 'PUT', - }, - } ); - - // Then the deletion step - expect( generator.next().value ).toMatchObject( { - type: 'API_FETCH', - request: { - url: pluginEndpoint, - method: 'DELETE', - }, - } ); - - expect( generator.next().value ).toEqual( { - type: 'REMOVE_INSTALLED_BLOCK_TYPE', - item: block, - } ); - - expect( generator.next() ).toEqual( { - value: undefined, - done: true, - } ); + const installedBlock = blockWithLinks( block, { + self: [ { href: pluginEndpoint } ], } ); - it( "should set a global notice if the plugin can't be deleted", () => { - const generator = uninstallBlockType( block ); - - expect( generator.next().value ).toMatchObject( { - type: 'API_FETCH', - request: { - url: pluginEndpoint, - method: 'PUT', - }, - } ); + it( 'should uninstall a block successfully', async () => { + const registry = createRegistryWithStores(); + + apiFetch.mockImplementation( async ( { url, method } ) => { + switch ( url ) { + case pluginEndpoint: + switch ( method ) { + case 'PUT': + case 'DELETE': + return; + default: + throw new Error( + `unexpected API endpoint method: ${ method }` + ); + } + default: + throw new Error( `unexpected API endpoint: ${ url }` ); + } + } ); + + // add installed block type that we're going to uninstall + registry + .dispatch( blockDirectoryStore ) + .addInstalledBlockType( installedBlock ); + + // uninstall the block + await registry + .dispatch( blockDirectoryStore ) + .uninstallBlockType( installedBlock ); + + // check that no error notice was displayed + const notices = registry.select( noticesStore ).getNotices(); + expect( notices ).toEqual( [] ); + + // verify that the block was uninstalled + const installedBlockTypes = registry + .select( blockDirectoryStore ) + .getInstalledBlockTypes(); + expect( installedBlockTypes ).toEqual( [] ); + } ); - expect( generator.next().value ).toMatchObject( { - type: 'API_FETCH', - request: { - url: pluginEndpoint, - method: 'DELETE', + it( "should set a global notice if the plugin can't be deleted", async () => { + const registry = createRegistryWithStores(); + + apiFetch.mockImplementation( async ( { url, method } ) => { + switch ( url ) { + case pluginEndpoint: + switch ( method ) { + case 'PUT': + return; + case 'DELETE': + throw { + code: 'rest_cannot_delete_active_plugin', + message: + 'Cannot delete an active plugin. Please deactivate it first.', + data: null, + }; + default: + throw new Error( + `unexpected API endpoint method: ${ method }` + ); + } + default: + throw new Error( `unexpected API endpoint: ${ url }` ); + } + } ); + + // uninstall the block + await registry + .dispatch( blockDirectoryStore ) + .uninstallBlockType( installedBlock ); + + // check that error notice was displayed + const notices = registry.select( noticesStore ).getNotices(); + expect( notices ).toMatchObject( [ + { + content: + 'Cannot delete an active plugin. Please deactivate it first.', }, - } ); - - const apiError = { - code: 'rest_cannot_delete_active_plugin', - message: - 'Cannot delete an active plugin. Please deactivate it first.', - data: null, - }; - expect( generator.throw( apiError ).value ).toMatchObject( { - type: '@@data/DISPATCH', - actionName: 'createErrorNotice', - storeKey: noticesStore.name, - } ); - - expect( generator.next() ).toEqual( { - value: undefined, - done: true, - } ); + ] ); } ); } ); } ); diff --git a/packages/block-directory/src/store/test/controls.js b/packages/block-directory/src/store/test/load-assets.js similarity index 92% rename from packages/block-directory/src/store/test/controls.js rename to packages/block-directory/src/store/test/load-assets.js index 43d4f56ac0fab..e72c1a084f553 100644 --- a/packages/block-directory/src/store/test/controls.js +++ b/packages/block-directory/src/store/test/load-assets.js @@ -1,7 +1,7 @@ /** * Internal dependencies */ -import { loadAsset } from '../controls'; +import { loadAsset } from '../load-assets'; describe( 'controls', () => { describe( 'loadAsset', () => {