diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6f4a4e0e0fc..9403d83f641 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -86,6 +86,8 @@ jobs: - name: Install dependencies run: yarn install - name: Basic Tests with ember-lts-3.8 + env: + CI: true run: yarn test:try-one ember-lts-3.8 releases: @@ -114,6 +116,8 @@ jobs: - name: Install dependencies run: yarn install - name: Basic tests with ${{ matrix.scenario }} + env: + CI: true run: yarn test:try-one ${{ matrix.scenario }} additional-scenarios: @@ -134,6 +138,8 @@ jobs: - name: Install dependencies run: yarn install - name: Basic tests with ${{ matrix.scenario }} + env: + CI: true run: yarn test:try-one ${{ matrix.scenario }} external-partners: diff --git a/packages/-ember-data/tests/dummy/app/models/foo.js b/packages/-ember-data/tests/dummy/app/models/foo.js new file mode 100644 index 00000000000..e77cb7824f1 --- /dev/null +++ b/packages/-ember-data/tests/dummy/app/models/foo.js @@ -0,0 +1,7 @@ +import Model, { attr, belongsTo, hasMany } from '@ember-data/model'; + +export default class extends Model { + @attr name; + @belongsTo('foo', { async: false }) parent; + @hasMany('foo', { async: false }) children; +} diff --git a/packages/-ember-data/tests/dummy/app/routes/application/route.ts b/packages/-ember-data/tests/dummy/app/routes/application/route.ts index d09f667b240..3a5330d6c2b 100644 --- a/packages/-ember-data/tests/dummy/app/routes/application/route.ts +++ b/packages/-ember-data/tests/dummy/app/routes/application/route.ts @@ -1,3 +1,16 @@ import Route from '@ember/routing/route'; -export default Route.extend({}); +export default Route.extend({ + model() { + // adding a model to the store to enable manually testing the debug-adapter + return this.store.push({ + data: { + id: 1, + type: 'foo', + attributes: { + name: 'taco', + }, + }, + }); + }, +}); diff --git a/packages/-record-data-encapsulation-test-app/tests/integration/smoke-test.js b/packages/-record-data-encapsulation-test-app/tests/integration/smoke-test.js index 950d9650991..32c601340db 100644 --- a/packages/-record-data-encapsulation-test-app/tests/integration/smoke-test.js +++ b/packages/-record-data-encapsulation-test-app/tests/integration/smoke-test.js @@ -1,6 +1,7 @@ /* global require */ import { module, test } from 'qunit'; import { setupTest } from 'ember-qunit'; +import Store from '@ember-data/store'; function assertPackageNotPresent(packageName, assert) { const entries = Object.keys(require.entries); @@ -33,6 +34,9 @@ function assertPackageNotPresent(packageName, assert) { module('Record-data Encapsulation - Smoke Tests', function(hooks) { setupTest(hooks); + hooks.beforeEach(function() { + this.owner.register('service:store', Store); + }); test('No @ember-data/record-data modules are present', function(assert) { assertPackageNotPresent('@ember-data/record-data', assert); diff --git a/packages/debug/addon/index.js b/packages/debug/addon/index.js index 0be51a0a10a..b8393cc1b95 100644 --- a/packages/debug/addon/index.js +++ b/packages/debug/addon/index.js @@ -8,7 +8,7 @@ import DataAdapter from '@ember/debug/data-adapter'; import { capitalize, underscore } from '@ember/string'; import { assert } from '@ember/debug'; import { get } from '@ember/object'; -import Model from '@ember-data/model'; +import { typesMapFor } from './setup'; /** Implements `@ember/debug/data-adapter` with for EmberData @@ -38,15 +38,72 @@ export default DataAdapter.extend({ ]; }, + _nameToClass(type) { + return get(this, 'store').modelFor(type); + }, + /** - Detect whether a class is a Model - @private - @method detect - @param {Model} typeClass - @return {Boolean} Whether the typeClass is a Model class or not + Fetch the model types and observe them for changes. + Maintains the list of model types without needing the Model package for detection. + @public + @method watchModelTypes + @param {Function} typesAdded Callback to call to add types. + Takes an array of objects containing wrapped types (returned from `wrapModelType`). + @param {Function} typesUpdated Callback to call when a type has changed. + Takes an array of objects containing wrapped types. + @return {Function} Method to call to remove all observers */ - detect(typeClass) { - return typeClass !== Model && Model.detect(typeClass); + watchModelTypes(typesAdded, typesUpdated) { + const store = get(this, 'store'); + const __createRecordData = store._createRecordData; + const _releaseMethods = []; + const discoveredTypes = typesMapFor(store); + + // Add any models that were added during initialization of the app, before the inspector was opened + discoveredTypes.forEach((_, type) => { + this.watchTypeIfUnseen(store, discoveredTypes, type, typesAdded, typesUpdated, _releaseMethods); + }); + + // Overwrite _createRecordData so newly added models will get added to the list + store._createRecordData = identifier => { + this.watchTypeIfUnseen(store, discoveredTypes, identifier.type, typesAdded, typesUpdated, _releaseMethods); + return __createRecordData.call(store, identifier); + }; + + let release = () => { + _releaseMethods.forEach(fn => fn()); + store._createRecordData = __createRecordData; + // reset the list so the models can be added if the inspector is re-opened + // the entries are set to false instead of removed, since the models still exist in the app + // we just need the inspector to become aware of them + discoveredTypes.forEach((value, key) => { + discoveredTypes.set(key, false); + }); + this.releaseMethods.removeObject(release); + }; + this.releaseMethods.pushObject(release); + return release; + }, + + /** + * Loop over the discovered types and use the callbacks from watchModelTypes to notify + * the consumer of this adapter about the mdoels. + * + * @param {store} store + * @param {Map} discoveredTypes + * @param {String} type + * @param {Function} typesAdded + * @param {Function} typesUpdated + * @param {Array} releaseMethods + */ + watchTypeIfUnseen(store, discoveredTypes, type, typesAdded, typesUpdated, releaseMethods) { + if (discoveredTypes.get(type) !== true) { + let klass = store.modelFor(type); + let wrapped = this.wrapModelType(klass, type); + releaseMethods.push(this.observeModelType(type, typesUpdated)); + typesAdded([wrapped]); + discoveredTypes.set(type, true); + } }, /** diff --git a/packages/debug/addon/setup.js b/packages/debug/addon/setup.js new file mode 100644 index 00000000000..45f94911861 --- /dev/null +++ b/packages/debug/addon/setup.js @@ -0,0 +1,28 @@ +import Store from '@ember-data/store'; +const StoreTypesMap = new WeakMap(); + +export function typesMapFor(store) { + let typesMap = StoreTypesMap.get(store); + + if (typesMap === undefined) { + typesMap = new Map(); + StoreTypesMap.set(store, typesMap); + } + + return typesMap; +} + +// override _createRecordData to add the known models to the typesMap +const __createRecordData = Store.prototype._createRecordData; +Store.prototype._createRecordData = function(identifier) { + const typesMap = typesMapFor(this); + if (!typesMap.has(identifier.type)) { + typesMap.set(identifier.type, false); + } + return __createRecordData.call(this, identifier); +}; + +export default { + name: '@ember-data/data-adapter', + initialize() {}, +}; diff --git a/packages/debug/app/initializers/ember-data-data-adapter.js b/packages/debug/app/initializers/ember-data-data-adapter.js new file mode 100644 index 00000000000..67b023ec8f2 --- /dev/null +++ b/packages/debug/app/initializers/ember-data-data-adapter.js @@ -0,0 +1 @@ +export { default } from '@ember-data/debug/setup';