Skip to content

Commit b337803

Browse files
committed
add tests for record iteration
1 parent 5c10bf4 commit b337803

File tree

6 files changed

+552
-9
lines changed

6 files changed

+552
-9
lines changed

internal-tooling/src/tasks/sync-scripts.ts

+9
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,15 @@ export async function main() {
135135
// ensure that the package.json scripts are up to date with defaults
136136
/////////////////////////////////////////////////////////////////////
137137

138+
if (project.isTest) {
139+
if (!('private' in project.pkg)) {
140+
project.pkg.private = true;
141+
project.isPrivate = true;
142+
pkgEdited = true;
143+
log(`\t\t🔧 Added private flag to package.json`);
144+
}
145+
}
146+
138147
if (!project.isPrivate) {
139148
if (!project.pkg.scripts) {
140149
project.pkg.scripts = {};

packages/schema-record/src/-private/record.ts

+40-4
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,17 @@ function isPathMatch(a: string[], b: string[]) {
7272
return a.length === b.length && a.every((v, i) => v === b[i]);
7373
}
7474

75+
function isNonEnumerableProp(prop: string | number | symbol) {
76+
return (
77+
prop === 'constructor' ||
78+
prop === 'prototype' ||
79+
prop === '__proto__' ||
80+
prop === 'toString' ||
81+
prop === 'toJSON' ||
82+
prop === 'toHTML'
83+
);
84+
}
85+
7586
const Editables = new WeakMap<SchemaRecord, SchemaRecord>();
7687
export class SchemaRecord {
7788
declare [RecordStore]: Store;
@@ -124,7 +135,12 @@ export class SchemaRecord {
124135

125136
const proxy = new Proxy(this, {
126137
ownKeys() {
127-
return Array.from(fields.keys());
138+
const identityKey = identityField?.name;
139+
const keys = Array.from(fields.keys());
140+
if (identityKey) {
141+
keys.unshift(identityKey);
142+
}
143+
return keys;
128144
},
129145

130146
has(target: SchemaRecord, prop: string | number | symbol) {
@@ -135,10 +151,17 @@ export class SchemaRecord {
135151
},
136152

137153
getOwnPropertyDescriptor(target, prop) {
138-
if (!fields.has(prop as string)) {
139-
throw new Error(`No field named ${String(prop)} on ${identifier.type}`);
140-
}
141154
const schemaForField = prop === identityField?.name ? identityField : fields.get(prop as string)!;
155+
assert(`No field named ${String(prop)} on ${identifier.type}`, schemaForField);
156+
157+
if (isNonEnumerableProp(prop)) {
158+
return {
159+
writable: false,
160+
enumerable: false,
161+
configurable: true,
162+
};
163+
}
164+
142165
switch (schemaForField.kind) {
143166
case 'derived':
144167
return {
@@ -248,6 +271,19 @@ export class SchemaRecord {
248271

249272
if (prop === Symbol.toPrimitive) return () => null;
250273

274+
if (prop === Symbol.iterator) {
275+
let fn = BoundFns.get(Symbol.iterator);
276+
if (!fn) {
277+
fn = function* () {
278+
for (const key in receiver) {
279+
yield [key, receiver[key as keyof typeof receiver]];
280+
}
281+
};
282+
BoundFns.set(Symbol.iterator, fn);
283+
}
284+
return fn;
285+
}
286+
251287
if (prop === 'constructor') {
252288
return SchemaRecord;
253289
}

packages/schema-record/src/-private/schema.ts

+10-1
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@ function _constructor(record: SchemaRecord) {
5555

5656
return (state._constructor = state._constructor || {
5757
name: `SchemaRecord<${recordIdentifierFor(record).type}>`,
58+
get modelName() {
59+
assert(`record.constructor.modelName is not available outside of legacy mode`, false);
60+
return undefined;
61+
},
5862
});
5963
}
6064
_constructor[Type] = '@constructor';
@@ -65,7 +69,12 @@ _constructor[Type] = '@constructor';
6569
*/
6670
export function withDefaults(schema: WithPartial<ResourceSchema, 'identity'>): ResourceSchema {
6771
schema.identity = schema.identity || DefaultIdentityField;
68-
schema.fields.push(TypeField, ConstructorField);
72+
73+
// because fields gets iterated in definition order,
74+
// we add TypeField to the beginning so that it will
75+
// appear right next to the identity field
76+
schema.fields.unshift(TypeField);
77+
schema.fields.push(ConstructorField);
6978
return schema as ResourceSchema;
7079
}
7180

tests/warp-drive__schema-record/package.json

+3-2
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,9 @@
1818
"build:tests": "IS_TESTING=true EMBER_CLI_TEST_COMMAND=true ember build --output-path=dist-test --suppress-sizes",
1919
"lint": "eslint . --quiet --cache --cache-strategy=content",
2020
"check:types": "glint",
21-
"start": "ember test --test-port=0 --serve --no-launch",
22-
"test": "ember test --test-port=0 --path=dist-test"
21+
"start": "bun run build:tests --watch",
22+
"test": "ember test --test-port=0 --path=dist-test",
23+
"test:start": "bun run test --serve --no-launch"
2324
},
2425
"devDependencies": {
2526
"@babel/core": "^7.26.9",

tests/warp-drive__schema-record/tests/legacy/legacy-mode-activation-test.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -135,8 +135,8 @@ module('Legacy Mode', function (hooks) {
135135
} catch (e) {
136136
assert.strictEqual(
137137
(e as Error).message,
138-
'Cannot access record.constructor.modelName on non-Legacy Schema Records.',
139-
'record.constructor.modelName throws'
138+
'record.constructor.modelName is not available outside of legacy mode',
139+
`record.constructor.modelName throws: ${(e as Error).message}`
140140
);
141141
}
142142
assert.strictEqual(record.constructor.name, 'SchemaRecord<user>', 'it has a useful constructor name');

0 commit comments

Comments
 (0)