Skip to content
Open
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
8 changes: 8 additions & 0 deletions src/runtime/shim-import.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { expect, it } from 'vitest';
import './shim.js';

// Kept apart from shim.spec.ts: using the device expect installs it as the
// global expect for the rest of the file.
it.fails('keeps host assertion counting after the shim is imported', () => {
expect.assertions(1);
});
43 changes: 43 additions & 0 deletions src/runtime/shim.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { describe, expect, it } from 'vitest';
import { expect as deviceExpect } from './shim.js';

describe('vitest shim', () => {
it('exposes the static expect API', () => {
deviceExpect({ id: 7, name: 'row' }).toEqual(
deviceExpect.objectContaining({ id: deviceExpect.any(Number) }),
);
deviceExpect('native').toEqual(deviceExpect.not.stringContaining('web'));

deviceExpect.extend({
toBeEven(received: number) {
return {
pass: received % 2 === 0,
message: () => `expected ${received} to be even`,
};
},
});
const custom = deviceExpect as unknown as Record<string, () => unknown>;
deviceExpect([2]).toEqual([custom.toBeEven()]);
expect(() => deviceExpect([3]).toEqual([custom.toBeEven()])).toThrow();
});

it('applies property operations to the device expect', () => {
Object.defineProperty(deviceExpect, 'fixedHelper', { value: 1 });
expect(Reflect.get(deviceExpect, 'fixedHelper')).toBe(1);
expect(Object.keys(deviceExpect)).toContain('objectContaining');

deviceExpect.extend({
toBeOdd: (received: number) => ({
pass: received % 2 === 1,
message: () => `expected ${received} to be odd`,
}),
});
expect(
delete (deviceExpect as unknown as Record<string, unknown>).toBeOdd,
).toBe(true);
expect('toBeOdd' in deviceExpect).toBe(false);

expect(() => Object.freeze(deviceExpect)).toThrow(TypeError);
expect(Object.keys(deviceExpect)).toContain('objectContaining');
});
});
49 changes: 44 additions & 5 deletions src/runtime/shim.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,48 @@ export {
test,
} from '@vitest/runner';

export const expect = ((...arguments_: unknown[]) =>
Reflect.apply(
// Proxy invariants require a property the proxy reports as non-configurable to
// exist, non-configurable, on the proxy target too.
function deviceExpectDescriptor(
target: object,
property: string | symbol,
): PropertyDescriptor | undefined {
const descriptor = Reflect.getOwnPropertyDescriptor(
getNativeScriptExpect(),
undefined,
arguments_,
)) as ExpectStatic;
property,
);
if (descriptor?.configurable === false) {
Reflect.defineProperty(target, property, descriptor);
}
return descriptor;
}

// Property operations reach the device expect, which is set up on first use
// rather than on import because setting it up replaces the global expect.
// Freezing, sealing or changing the prototype is refused: those would apply to
// the proxy target alone and break the invariants of every later operation.
export const expect = new Proxy(
(...arguments_: unknown[]) =>
Reflect.apply(getNativeScriptExpect(), undefined, arguments_),
{
get: (_target, property) => Reflect.get(getNativeScriptExpect(), property),
set: (_target, property, value) =>
Reflect.set(getNativeScriptExpect(), property, value),
has: (_target, property) => Reflect.has(getNativeScriptExpect(), property),
deleteProperty: (_target, property) =>
Reflect.deleteProperty(getNativeScriptExpect(), property),
ownKeys: () => Reflect.ownKeys(getNativeScriptExpect()),
getOwnPropertyDescriptor: deviceExpectDescriptor,
defineProperty: (target, property, attributes) => {
if (
!Reflect.defineProperty(getNativeScriptExpect(), property, attributes)
) {
return false;
}
deviceExpectDescriptor(target, property);
return true;
},
preventExtensions: () => false,
setPrototypeOf: () => false,
},
) as unknown as ExpectStatic;