diff --git a/ember-mirage/src/create-config.js b/ember-mirage/src/create-config.js new file mode 100644 index 0000000..41ac9fd --- /dev/null +++ b/ember-mirage/src/create-config.js @@ -0,0 +1,35 @@ +import { _utilsInflectorCamelize as camelize } from 'miragejs'; + +async function getDefaultExports(modules) { + return Promise.all( + Object.entries(modules).map(([path, importFn]) => + importFn().then((module) => ({ path, defaultExport: module.default })), + ), + ); +} + +export function entityName(path) { + const filenameWithoutExt = path.split('/').pop().split('.')[0]; + return camelize(filenameWithoutExt); +} + +export async function createConfig(mirageImportMap = {}) { + return { + factories: await importEntities(mirageImportMap.factories), + fixtures: await importEntities(mirageImportMap.fixtures), + models: await importEntities(mirageImportMap.models), + scenarios: await importEntities(mirageImportMap.scenarios), + serializers: await importEntities(mirageImportMap.serializers), + identityManagers: await importEntities(mirageImportMap.identityManagers), + }; +} + +async function importEntities(importMap = {}) { + const modules = await getDefaultExports(importMap); + + return modules.reduce((acc, { path, defaultExport }) => { + const configName = entityName(path); + acc[configName] = defaultExport; + return acc; + }, {}); +} diff --git a/ember-mirage/src/ember-data.js b/ember-mirage/src/ember-data.js new file mode 100644 index 0000000..041d27a --- /dev/null +++ b/ember-mirage/src/ember-data.js @@ -0,0 +1,113 @@ +import { Model, belongsTo, hasMany } from 'miragejs'; +import EmberDataSerializer from './ember-data-serializer'; +import { entityName } from './create-config'; + +const MirageModelCache = {}; +const MirageSerializerCache = {}; + +export function importEmberDataModels(store, importMap = {}) { + return Object.keys(importMap).reduce((acc, path) => { + const configName = entityName(path); + acc[configName] = createMirageModel(store, configName); + return acc; + }, {}); +} + +// TODO: Replace with original `applyEmberDataSerializers` function +export function importEmberDataSerializers( + mirageSerializers, + emberDataConfig = {}, +) { + const { store, serializers = {} } = emberDataConfig; + return Object.keys(serializers).reduce((acc, path) => { + const configName = entityName(path); + acc[configName] = createMirageSerializer( + store, + mirageSerializers, + configName, + ); + return acc; + }, {}); +} + +function createMirageModel(store, modelName) { + if (MirageModelCache[modelName]) { + return MirageModelCache[modelName]; + } + + const model = store.modelFor(modelName); + const relationships = {}; + + model.eachRelationship((name, r) => { + if (r.kind === 'belongsTo') { + relationships[name] = belongsTo(r.type, r.options); + } else if (r.kind === 'hasMany') { + relationships[name] = hasMany(r.type, r.options); + } + }); + const mirageModel = Model.extend(relationships); + + MirageModelCache[modelName] = mirageModel; + + return mirageModel; +} + +export function createMirageSerializer(store, serializers, serializerName) { + if (MirageSerializerCache[serializerName]) { + return MirageSerializerCache[serializerName]; + } + + let mirageSerializer = + serializers[serializerName] || + serializers.application || + EmberDataSerializer; + + let dsSerializer = store.serializerFor(serializerName); + + let transforms; + let primaryKey = dsSerializer.primaryKey; + let attrs = dsSerializer.attrs; + if (primaryKey || attrs) { + if (attrs) { + let serializer = mirageSerializer.create + ? mirageSerializer.create() + : new mirageSerializer(); + + transforms = serializer.transforms || {}; + + Object.keys(attrs).forEach((key) => { + let transform = attrs[key]; + let serializerTransform = serializer.transforms + ? serializer.transforms[key] + : {}; + let resolvedTransform = + typeof attrs[key] === 'string' + ? { + key: attrs[key], + } + : { + key: attrs[key].key, + }; + + if (transform.serialize !== undefined) { + resolvedTransform.deserialize = transform.serialize; + } + + if (transform.deserialize !== undefined) { + resolvedTransform.serialize = transform.deserialize; + } + + transforms[key] = Object.assign(resolvedTransform, serializerTransform); + }); + } + + mirageSerializer = mirageSerializer.extend({ + primaryKey, + transforms, + }); + } + + MirageSerializerCache[serializerName] = mirageSerializer; + + return mirageSerializer; +} diff --git a/ember-mirage/src/index.js b/ember-mirage/src/index.js index 320ca9b..d59db33 100644 --- a/ember-mirage/src/index.js +++ b/ember-mirage/src/index.js @@ -1 +1 @@ -export { default as startMirage } from './start-mirage'; +export { createConfig, entityName } from './create-config.js'; diff --git a/ember-mirage/src/start-mirage.js b/ember-mirage/src/start-mirage.js deleted file mode 100644 index 69d4bf8..0000000 --- a/ember-mirage/src/start-mirage.js +++ /dev/null @@ -1,44 +0,0 @@ -import { pluralize, singularize } from 'active-inflector'; - -import assert from './assert'; - -export default function startMirage( - makeServer, - { owner, env, ...otherOptions } = {}, -) { - assert('There is no makeServer function passed to startMirage', makeServer); - - assert( - 'Mirage config default exported function must at least one parameter', - makeServer.length > 0, - ); - - if (!env) { - assert( - 'You must pass `owner` to startMirage() to lookup environment', - owner, - ); - env = owner.resolveRegistration('config:environment'); - } - - let environment = env.environment; - - let options = { - env, - environment, - inflector: { singularize, pluralize }, - ...otherOptions, - }; - - let server = makeServer(options); - - // Check to see if mirageLogging is on the URL. If so, enable logging on the server - if ( - typeof location !== 'undefined' && - location.search.indexOf('mirageLogging') !== -1 - ) { - server.logging = true; - } - - return server; -} diff --git a/ember-mirage/src/test-support/setup-mirage.js b/ember-mirage/src/test-support/setup-mirage.js index 06f37b4..f97e632 100644 --- a/ember-mirage/src/test-support/setup-mirage.js +++ b/ember-mirage/src/test-support/setup-mirage.js @@ -1,18 +1,24 @@ +import { assert } from '@ember/debug'; import { settled } from '@ember/test-helpers'; +import { createServer as _createServer } from 'miragejs'; -import startMirage from '../start-mirage'; +export function setupMirage(hooks = self, { createServer, config }) { + assert( + `Unexpected arity for setupMirage. Expected 2 (hooks, { createServer, or config })`, + arguments.length <= 2 && arguments.length > 0, + ); + assert( + `Second argument to setupMirage must be an object and not null`, + typeof arguments[1] === 'object' && arguments[1] !== null, + ); + assert( + `Second argument to setupMirage must on or both of createServer and/or config. You passed ${Object.keys(arguments[1]).join(', ')}`, + 'createServer' in arguments[1] || 'config' in arguments[1], + ); -/** - Used to set up mirage for a test. Must be called after one of the - `ember-qunit` `setup*Test()` methods. It starts the server and sets - `this.server` to point to it, and shuts the server down when the test - finishes. + createServer ??= _createServer; - NOTE: the `hooks = self` is for mocha support - @hide - */ -export function setupMirage(hooks = self, { makeServer, ...options }) { - hooks.beforeEach(function () { + hooks.beforeEach(async function () { if (!this.owner) { throw new Error( 'You must call one of the ember-qunit setupTest(),' + @@ -21,7 +27,7 @@ export function setupMirage(hooks = self, { makeServer, ...options }) { ); } - this.server = startMirage(makeServer, { owner: this.owner, ...options }); + this.server = await createServer(config ?? {}); }); hooks.afterEach(function () { diff --git a/test-app/app/mirage/servers/create-config.js b/test-app/app/mirage/servers/create-config.js new file mode 100644 index 0000000..dcc3cf8 --- /dev/null +++ b/test-app/app/mirage/servers/create-config.js @@ -0,0 +1,32 @@ +//import { createConfig } from 'ember-mirage'; +//import defaultRoutes from '../routes/default'; +// +//export async function config(store) { +// const { importEmberDataModels, importEmberDataSerializers } = await import( +// 'ember-mirage/ember-data' +// ); +// +// const mirageConfig = await createConfig({ +// factories: import.meta.glob('./factories/*'), +// fixtures: import.meta.glob('./fixtures/*'), +// // Don't import our mirage things that will be auto-discovered +// // models: import.meta.glob('./models/*'), +// // serializers: import.meta.glob('./serializers/*'), +// identityManagers: import.meta.glob('./identity-managers/*'), +// }); +// +// return { +// ...mirageConfig, +// models: { +// // use ember-data model auto discovery +// ...importEmberDataModels(store, import.meta.glob('../models/*')), +// ...mirageConfig.models, +// }, +// // apply ember-data serializer config details to mirage serializers +// serializers: importEmberDataSerializers(store, mirageConfig.serializers), +// routes() { +// this.config({ routes: defaultRoutes }); +// }, +// }; +//} +// diff --git a/test-app/app/mirage/servers/ember-data-create-config.js b/test-app/app/mirage/servers/ember-data-create-config.js new file mode 100644 index 0000000..e69de29 diff --git a/test-app/app/mirage/servers/default.js b/test-app/app/mirage/servers/manual.js similarity index 91% rename from test-app/app/mirage/servers/default.js rename to test-app/app/mirage/servers/manual.js index 3a95e92..31596c9 100644 --- a/test-app/app/mirage/servers/default.js +++ b/test-app/app/mirage/servers/manual.js @@ -1,5 +1,3 @@ -import { createServer } from 'miragejs'; - import factories from '../factories'; import mirageModels from '../models'; import defaultRoutes from '../routes/default'; @@ -28,7 +26,7 @@ export default function (config) { }, }; - return createServer(finalConfig); + return finalConfig; } function defineRoutes() { diff --git a/test-app/tests/acceptance/example-test.js b/test-app/tests/acceptance/example-test.js index 8ee68ce..b78a112 100644 --- a/test-app/tests/acceptance/example-test.js +++ b/test-app/tests/acceptance/example-test.js @@ -1,7 +1,8 @@ import { currentURL, visit } from '@ember/test-helpers'; +import { createServer } from 'miragejs'; import { module, test } from 'qunit'; -import mirageConfig from 'test-app/mirage/servers/default'; +import mirageConfig from 'test-app/mirage/servers/manual'; import { setupApplicationTest } from 'test-app/tests/helpers'; import { setupMirage } from 'test-app/tests/test-support/mirage'; @@ -11,6 +12,7 @@ module('Acceptance | example test', function (hooks) { module('Test with default config', function (hooks) { setupMirage(hooks); + test('visiting /example', async function (assert) { await visit('/example'); @@ -19,7 +21,17 @@ module('Acceptance | example test', function (hooks) { }); module('Test with imported config', function (hooks) { - setupMirage(hooks, { makeserver: mirageConfig }); + setupMirage(hooks, { config: mirageConfig }); + test('visiting /example', async function (assert) { + await visit('/example'); + + assert.strictEqual(currentURL(), '/example'); + }); + }); + + module('Test with custom server', function (hooks) { + setupMirage(hooks, { createServer: () => createServer(mirageConfig) }); + test('visiting /example', async function (assert) { await visit('/example'); diff --git a/test-app/tests/test-support/mirage.js b/test-app/tests/test-support/mirage.js index 6f72b8f..0dc03ee 100644 --- a/test-app/tests/test-support/mirage.js +++ b/test-app/tests/test-support/mirage.js @@ -1,10 +1,11 @@ -import mirageConfig from 'test-app/mirage/servers/default'; +import mirageConfig from 'test-app/mirage/servers/manual'; import { setupMirage as _setupMirage } from 'ember-mirage/test-support'; -export function setupMirage(hooks, options) { - options = options || {}; - options.makeServer = options.makeServer || mirageConfig; +import { createServer as _createServer } from 'miragejs'; - return _setupMirage(hooks, options); +export function setupMirage(hooks, { createServer, config } = {}) { + createServer ??= _createServer; + config ??= mirageConfig; + return _setupMirage(hooks, { createServer, config }); }