Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions .agents/skills/data-client-rest/references/Fixtures.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions .agents/skills/data-client-rest/references/Fixtures.vue.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions .agents/skills/data-client-vue-testing/references/Fixtures.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 25 additions & 0 deletions .changeset/fixture-args-readonly.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
'@data-client/core': patch
'@data-client/test': patch
'@data-client/react': patch
'@data-client/vue': patch
---

Fixture `args` accept readonly tuples

Fixtures written with `args: [...] as const` now type-check when passed to [MockResolver](https://dataclient.io/docs/api/MockResolver), `renderDataHook()`, Vue's `renderDataCompose()` or `mockInitialState()`. Before, TypeScript rejected them with "The type 'readonly [...]' is 'readonly' and cannot be assigned to the mutable type", so you had to drop `as const` or cast.

```ts
const fixtures = [
{
endpoint: TodoResource.getList,
args: [{ userId: 1 }] as const,
response: [{ id: 1, title: 'Write tests', userId: 1 }],
},
];

// Before: type error on `fixtures`. After: works as written
<MockResolver fixtures={fixtures}>
<TodoList />
</MockResolver>;
```
4 changes: 2 additions & 2 deletions docs/core/api/Fixtures.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export interface SuccessFixture<
E extends EndpointInterface = EndpointInterface,
> {
readonly endpoint: E;
readonly args: Parameters<E>;
readonly args: Readonly<Parameters<E>>;
readonly response:
| ResolveType<E>
| ((...args: Parameters<E>) => ResolveType<E>);
Expand Down Expand Up @@ -74,7 +74,7 @@ export interface ErrorFixture {
```ts
export interface ErrorFixture<E extends EndpointInterface = EndpointInterface> {
readonly endpoint: E;
readonly args: Parameters<E>;
readonly args: Readonly<Parameters<E>>;
readonly response: any;
readonly error: true;
/** Number of milliseconds to wait before resolving */
Expand Down
2 changes: 1 addition & 1 deletion docs/core/api/MockResolver.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ const results = [
// fixture
{
endpoint: ArticleResource.getList,
args: [{ maxResults: 10 }],
args: [{ maxResults: 10 }] as const,
response: [
{
id: 5,
Expand Down
8 changes: 4 additions & 4 deletions docs/core/guides/unit-testing-components.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export default {
full: [
{
endpoint: ArticleResource.getList,
args: [{ maxResults: 10 }],
args: [{ maxResults: 10 }] as const,
response: [
{
id: 5,
Expand All @@ -49,7 +49,7 @@ export default {
},
{
endpoint: ArticleResource.update,
args: [{ id: 532 }],
args: [{ id: 532 }] as const,
response({ id }, body) {
return {
id,
Expand All @@ -61,14 +61,14 @@ export default {
empty: [
{
endpoint: ArticleResource.getList,
args: [{ maxResults: 10 }],
args: [{ maxResults: 10 }] as const,
response: [],
},
],
error: [
{
endpoint: ArticleResource.getList,
args: [{ maxResults: 10 }],
args: [{ maxResults: 10 }] as const,
response: { message: 'Bad request', status: 400, name: 'Not Found' },
error: true,
},
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/mock/fixtureTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export interface SuccessFixtureEndpoint<
E extends EndpointInterface & { update?: Updater } = EndpointInterface,
> {
readonly endpoint: E;
readonly args: Parameters<E>;
readonly args: Readonly<Parameters<E>>;
readonly response:
ResolveType<E> | ((...args: Parameters<E>) => ResolveType<E>);
readonly error?: false;
Expand Down Expand Up @@ -84,7 +84,7 @@ export interface ErrorFixtureEndpoint<
E extends EndpointInterface & { update?: Updater } = EndpointInterface,
> {
readonly endpoint: E;
readonly args: Parameters<E>;
readonly args: Readonly<Parameters<E>>;
readonly response: any;
readonly error: true;
/** Number of miliseconds to wait before resolving */
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/mock/mockState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export default function mockInitialState(

function dispatchFixture(
fixture: Fixture,
args: any[],
args: readonly any[],
controller: Controller,
fetchedAt?: number,
) {
Expand Down
4 changes: 2 additions & 2 deletions packages/test/src/fixtureTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export interface SuccessFixtureEndpoint<
E extends EndpointInterface & { update?: Updater } = EndpointInterface,
> {
readonly endpoint: E;
readonly args: Parameters<E>;
readonly args: Readonly<Parameters<E>>;
readonly response:
ResolveType<E> | ((...args: Parameters<E>) => ResolveType<E>);
readonly error?: false;
Expand Down Expand Up @@ -84,7 +84,7 @@ export interface ErrorFixtureEndpoint<
E extends EndpointInterface & { update?: Updater } = EndpointInterface,
> {
readonly endpoint: E;
readonly args: Parameters<E>;
readonly args: Readonly<Parameters<E>>;
readonly response: any;
readonly error: true;
/** Number of miliseconds to wait before resolving */
Expand Down
1 change: 1 addition & 0 deletions scripts/typeperf/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ When a count drops more than 10% below its budget, the check says so; re-record
| setValues | `ctrl.set()` values on a Union, Collection and big Entity |
| setUpdaters | `ctrl.set()` updaters spreading `prev` on a 30-member Union |
| redux | `prepareStore()`'s store passed to react-redux's `Provider` and `ExternalDataProvider` |
| fixtures | 100 sets of `@data-client/test` fixtures (mutable and `as const` args) and interceptors passed to `MockResolver` and `mockInitialState()` |
| setInvalidate | `ctrl.set()` single and batch Invalidate rows on a 30-member Union and big Entity |
| patheq | path types against [patheq/orig.ts](./patheq/orig.ts) on fixed-seed fuzzed paths |

Expand Down
1 change: 1 addition & 0 deletions scripts/typeperf/budget.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"setUpdaters": 66031,
"redux": 12986,
"setInvalidate": 7325,
"fixtures": 76837,
"patheq": 934395
},
"typescript": "7.0.2"
Expand Down
35 changes: 34 additions & 1 deletion scripts/typeperf/gen.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,40 @@ declare function provide<A extends { type: string }, S>(props: ProviderProps<A,
return s;
};

// 11. path types vs their frozen pre-#4173 implementation (a type error is a mismatch)
// 11. @data-client/test fixtures and interceptors (mutable and `as const` args) passed to MockResolver
scen.fixtures = () => {
let s = `import { Entity, resource } from '@data-client/rest';
import { MockResolver, mockInitialState } from '@data-client/test';
import type { Fixture, Interceptor } from '@data-client/test';
`;
// a few resources, so the count tracks Fixture/Interceptor/MockResolver typing rather than resource()
const R = 4;
for (let r = 0; r < R; r++)
s += `export class E${r} extends Entity {\n id = '';\n${fields(20)}\n static key = 'E${r}';\n}
export const R${r} = resource({ path: '/e${r}/:id', schema: E${r}, searchParams: {} as { q?: string } | undefined });
`;
for (let i = 0; i < 100 * N; i++) {
const r = `R${i % R}`;
s += `export const fixtures${i} = [
{ endpoint: ${r}.get, args: [{ id: '${i}' }] as const, response: { id: '${i}', f0: 'a' } },
{ endpoint: ${r}.getList, args: [{ q: 'x${i}' }], response: [{ id: '${i}' }] },
{ endpoint: ${r}.update, args: [{ id: '${i}' }, { f0: 'b' }] as const, response: { id: '${i}', f0: 'b' } },
{ endpoint: ${r}.delete, args: [{ id: '${i}' }] as const, response: { message: 'nope' }, error: true },
];
export const typed${i}: Fixture<typeof ${r}.get> = { endpoint: ${r}.get, args: [{ id: '${i}' }] as const, response: { id: '${i}' } };
export const interceptor${i}: Interceptor<{ n: number }> = {
endpoint: ${r}.get,
response({ id }) { return { id, f0: \`\${this.n++}\` }; },
delay: () => ${i},
};
MockResolver({ fixtures: [...fixtures${i}, typed${i}, interceptor${i}], getInitialInterceptorData: () => ({ n: 0 }), children: null });
mockInitialState([...fixtures${i}, typed${i}]);
`;
}
return s;
};

// 12. path types vs their frozen pre-#4173 implementation (a type error is a mismatch)
scen.patheq = patheq;

/** Writes scenarios/<name>/ for the named fixtures (all when empty); returns the names */
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading