From bf607cc1f7f293d68b7155e82d1caaad5a7ce23d Mon Sep 17 00:00:00 2001 From: Yaroslav Serhieiev Date: Thu, 15 Feb 2024 11:05:25 +0200 Subject: [PATCH] feat: .defaults, operation --- .../27.x.x/env-1/hook-nesting.json | 22 ++++++++++++++++ .../27.x.x/env-N/hook-nesting.json | 22 ++++++++++++++++ .../28.x.x/env-1/hook-nesting.json | 22 ++++++++++++++++ .../28.x.x/env-N/hook-nesting.json | 22 ++++++++++++++++ .../29.x.x/env-1/hook-nesting.json | 22 ++++++++++++++++ .../29.x.x/env-N/hook-nesting.json | 22 ++++++++++++++++ e2e/__tests__/default/hook-nesting.js | 5 +++- package-e2e/test.cjs | 3 ++- package-e2e/test.mjs | 3 ++- package-e2e/test.ts | 25 ++++++++++++++----- package.json | 6 ++--- src/index.ts | 7 ++++++ .../__snapshots__/integration.test.ts.snap | 6 ++--- src/metadata/containers/BaseMetadata.ts | 24 ++++++++++++++++++ src/metadata/dsl/MetadataDSL.ts | 9 +++++++ src/metadata/events/WriteMetadataEvent.ts | 2 +- src/metadata/types/Metadata.ts | 5 ++-- 17 files changed, 209 insertions(+), 18 deletions(-) diff --git a/e2e/__fixtures__/27.x.x/env-1/hook-nesting.json b/e2e/__fixtures__/27.x.x/env-1/hook-nesting.json index e342bd4..c3a946c 100644 --- a/e2e/__fixtures__/27.x.x/env-1/hook-nesting.json +++ b/e2e/__fixtures__/27.x.x/env-1/hook-nesting.json @@ -45,6 +45,28 @@ }, "operation": "merge" }, + { + "type": "write_metadata", + "testFilePath": "__tests__/default/hook-nesting.js", + "targetId": "describe_1", + "path": "vendor.author", + "value": { + "name": "John Doe", + "email": "john.doe@example.com" + }, + "operation": "defaults" + }, + { + "type": "write_metadata", + "testFilePath": "__tests__/default/hook-nesting.js", + "targetId": "describe_1", + "path": "vendor.author", + "value": { + "name": "Impostor", + "email": "impostor@example.fake" + }, + "operation": "defaults" + }, { "type": "write_metadata", "testFilePath": "__tests__/default/hook-nesting.js", diff --git a/e2e/__fixtures__/27.x.x/env-N/hook-nesting.json b/e2e/__fixtures__/27.x.x/env-N/hook-nesting.json index e342bd4..c3a946c 100644 --- a/e2e/__fixtures__/27.x.x/env-N/hook-nesting.json +++ b/e2e/__fixtures__/27.x.x/env-N/hook-nesting.json @@ -45,6 +45,28 @@ }, "operation": "merge" }, + { + "type": "write_metadata", + "testFilePath": "__tests__/default/hook-nesting.js", + "targetId": "describe_1", + "path": "vendor.author", + "value": { + "name": "John Doe", + "email": "john.doe@example.com" + }, + "operation": "defaults" + }, + { + "type": "write_metadata", + "testFilePath": "__tests__/default/hook-nesting.js", + "targetId": "describe_1", + "path": "vendor.author", + "value": { + "name": "Impostor", + "email": "impostor@example.fake" + }, + "operation": "defaults" + }, { "type": "write_metadata", "testFilePath": "__tests__/default/hook-nesting.js", diff --git a/e2e/__fixtures__/28.x.x/env-1/hook-nesting.json b/e2e/__fixtures__/28.x.x/env-1/hook-nesting.json index e342bd4..c3a946c 100644 --- a/e2e/__fixtures__/28.x.x/env-1/hook-nesting.json +++ b/e2e/__fixtures__/28.x.x/env-1/hook-nesting.json @@ -45,6 +45,28 @@ }, "operation": "merge" }, + { + "type": "write_metadata", + "testFilePath": "__tests__/default/hook-nesting.js", + "targetId": "describe_1", + "path": "vendor.author", + "value": { + "name": "John Doe", + "email": "john.doe@example.com" + }, + "operation": "defaults" + }, + { + "type": "write_metadata", + "testFilePath": "__tests__/default/hook-nesting.js", + "targetId": "describe_1", + "path": "vendor.author", + "value": { + "name": "Impostor", + "email": "impostor@example.fake" + }, + "operation": "defaults" + }, { "type": "write_metadata", "testFilePath": "__tests__/default/hook-nesting.js", diff --git a/e2e/__fixtures__/28.x.x/env-N/hook-nesting.json b/e2e/__fixtures__/28.x.x/env-N/hook-nesting.json index e342bd4..c3a946c 100644 --- a/e2e/__fixtures__/28.x.x/env-N/hook-nesting.json +++ b/e2e/__fixtures__/28.x.x/env-N/hook-nesting.json @@ -45,6 +45,28 @@ }, "operation": "merge" }, + { + "type": "write_metadata", + "testFilePath": "__tests__/default/hook-nesting.js", + "targetId": "describe_1", + "path": "vendor.author", + "value": { + "name": "John Doe", + "email": "john.doe@example.com" + }, + "operation": "defaults" + }, + { + "type": "write_metadata", + "testFilePath": "__tests__/default/hook-nesting.js", + "targetId": "describe_1", + "path": "vendor.author", + "value": { + "name": "Impostor", + "email": "impostor@example.fake" + }, + "operation": "defaults" + }, { "type": "write_metadata", "testFilePath": "__tests__/default/hook-nesting.js", diff --git a/e2e/__fixtures__/29.x.x/env-1/hook-nesting.json b/e2e/__fixtures__/29.x.x/env-1/hook-nesting.json index 3f2b4e0..64fedf9 100644 --- a/e2e/__fixtures__/29.x.x/env-1/hook-nesting.json +++ b/e2e/__fixtures__/29.x.x/env-1/hook-nesting.json @@ -45,6 +45,28 @@ }, "operation": "merge" }, + { + "type": "write_metadata", + "testFilePath": "__tests__/default/hook-nesting.js", + "targetId": "describe_1", + "path": "vendor.author", + "value": { + "name": "John Doe", + "email": "john.doe@example.com" + }, + "operation": "defaults" + }, + { + "type": "write_metadata", + "testFilePath": "__tests__/default/hook-nesting.js", + "targetId": "describe_1", + "path": "vendor.author", + "value": { + "name": "Impostor", + "email": "impostor@example.fake" + }, + "operation": "defaults" + }, { "type": "write_metadata", "testFilePath": "__tests__/default/hook-nesting.js", diff --git a/e2e/__fixtures__/29.x.x/env-N/hook-nesting.json b/e2e/__fixtures__/29.x.x/env-N/hook-nesting.json index 3f2b4e0..64fedf9 100644 --- a/e2e/__fixtures__/29.x.x/env-N/hook-nesting.json +++ b/e2e/__fixtures__/29.x.x/env-N/hook-nesting.json @@ -45,6 +45,28 @@ }, "operation": "merge" }, + { + "type": "write_metadata", + "testFilePath": "__tests__/default/hook-nesting.js", + "targetId": "describe_1", + "path": "vendor.author", + "value": { + "name": "John Doe", + "email": "john.doe@example.com" + }, + "operation": "defaults" + }, + { + "type": "write_metadata", + "testFilePath": "__tests__/default/hook-nesting.js", + "targetId": "describe_1", + "path": "vendor.author", + "value": { + "name": "Impostor", + "email": "impostor@example.fake" + }, + "operation": "defaults" + }, { "type": "write_metadata", "testFilePath": "__tests__/default/hook-nesting.js", diff --git a/e2e/__tests__/default/hook-nesting.js b/e2e/__tests__/default/hook-nesting.js index 6baa447..9014055 100644 --- a/e2e/__tests__/default/hook-nesting.js +++ b/e2e/__tests__/default/hook-nesting.js @@ -1,4 +1,4 @@ -const { metadata, $Assign, $Push, $Set, $Merge, $Unshift } = require('jest-metadata'); +const { metadata, $Assign, $Defaults, $Push, $Set, $Merge, $Unshift } = require('jest-metadata'); let now = 1672524000000; @@ -14,6 +14,7 @@ const actions = { const $Description = (text) => $Set('vendor.description', text); const $Maintainer = (name, email) => $Assign('vendor.maintainer', { name, email }); +const $Author = (name, email) => $Defaults('vendor.author', { name, email }); const $Lead = (name, email) => $Merge('vendor.lead', { name, email }); const $Tag = (value) => $Push(['vendor', 'labels'], value); const $Flaky = () => $Unshift(['vendor', 'labels'], 'flaky'); @@ -22,6 +23,8 @@ const step = (text) => metadata.push('vendor.steps', [{ text, startedAt: now }]) $Maintainer('Jane Smith', 'jane.smith@example.com'); $Lead('Samantha Jones', 'samantha.jones@example.com'); +$Author('John Doe', 'john.doe@example.com'); +$Author('Impostor', 'impostor@example.fake'); $Description('This is a sample test suite.'); describe('Login flow', () => { $Description('Prepare the environment'); diff --git a/package-e2e/test.cjs b/package-e2e/test.cjs index 90987a1..05eecd7 100644 --- a/package-e2e/test.cjs +++ b/package-e2e/test.cjs @@ -1,12 +1,13 @@ const assert = require('assert'); -const { metadata, state, $Set, $Push, $Merge, $Assign, $Unshift } = require('jest-metadata'); +const { metadata, state, $Set, $Push, $Merge, $Assign, $Defaults, $Unshift } = require('jest-metadata'); assert(typeof metadata === 'object', 'jest-metadata should export `metadata` object'); assert(typeof state === 'object', 'jest-metadata should export `state` object'); assert(typeof $Set === 'function', 'jest-metadata should export $Set function as a named export'); assert(typeof $Push === 'function', 'jest-metadata should export $Push function as a named export'); assert(typeof $Merge === 'function', 'jest-metadata should export $Merge function as a named export'); assert(typeof $Assign === 'function', 'jest-metadata should export $Assign function as a named export'); +assert(typeof $Defaults === 'function', 'jest-metadata should export $Defaults function as a named export'); assert(typeof $Unshift === 'function', 'jest-metadata should export $Unshift function as a named export'); const { events } = require('jest-metadata/debug'); diff --git a/package-e2e/test.mjs b/package-e2e/test.mjs index 5a94f95..d91ff66 100644 --- a/package-e2e/test.mjs +++ b/package-e2e/test.mjs @@ -1,5 +1,5 @@ import assert from 'assert'; -import { $Set, $Push, $Merge, $Assign, $Unshift, state, metadata } from 'jest-metadata'; +import { $Set, $Push, $Merge, $Assign, $Defaults, $Unshift, state, metadata } from 'jest-metadata'; import { events } from 'jest-metadata/debug'; import JsdomTestEnvironment from 'jest-metadata/environment-jsdom'; import NodeTestEnvironment from 'jest-metadata/environment-node'; @@ -12,6 +12,7 @@ assert(typeof $Set === 'function', 'jest-metadata should export `$Set` function assert(typeof $Push === 'function', 'jest-metadata should export `$Push` function as a named export'); assert(typeof $Merge === 'function', 'jest-metadata should export `$Merge` function as a named export'); assert(typeof $Assign === 'function', 'jest-metadata should export `$Assign` function as a named export'); +assert(typeof $Defaults === 'function', 'jest-metadata should export `$Defaults` function as a named export'); assert(typeof $Unshift === 'function', 'jest-metadata should export `$Unshift` function as a named export'); assert(typeof events === 'object', 'jest-metadata/debug should export `events` object'); diff --git a/package-e2e/test.ts b/package-e2e/test.ts index 2bb8542..f3714df 100644 --- a/package-e2e/test.ts +++ b/package-e2e/test.ts @@ -1,4 +1,4 @@ -import { $Set, $Push, $Merge, $Assign, $Unshift, state, metadata } from 'jest-metadata'; +import { $Set, $Push, $Merge, $Assign, $Defaults, $Unshift, state, metadata } from 'jest-metadata'; import { events } from 'jest-metadata/debug'; import type { GlobalMetadata, Metadata } from 'jest-metadata'; import JestMetadataReporter, { query, JestMetadataReporter as JestMetadataReporterNamed } from 'jest-metadata/reporter'; @@ -12,11 +12,24 @@ function assertType(_actual: T, _other?: T): void { assertType(state); assertType(metadata); -assertType($Set); -assertType($Push); -assertType($Merge); -assertType($Assign); -assertType($Unshift); + +$Set('path', 'value' as unknown); +$Set(['path'], 'value' as unknown); + +$Push('path', -1, '2', true); +$Push(['path'], -1, '2', true); + +$Unshift('path', -1, '2', true); +$Unshift(['path'], -1, '2', true); + +$Merge('path', { key: 'value' }); +$Merge(['path'], { key: 'value' }); + +$Assign('path', { key: 'value' }); +$Assign(['path'], { key: 'value' }); + +$Defaults('path', { key: 'value' }); +$Defaults(['path'], { key: 'value' }); assertType(events); diff --git a/package.json b/package.json index 4eccdf3..0356cfa 100644 --- a/package.json +++ b/package.json @@ -89,9 +89,9 @@ }, "homepage": "https://github.com/wix-incubator/jest-metadata#readme", "dependencies": { - "bunyamin": "^1.5.0", + "bunyamin": "^1.5.2", "funpermaproxy": "^1.1.0", - "jest-environment-emit": "^1.0.1", + "jest-environment-emit": "^1.0.6", "lodash.merge": "^4.6.2", "node-ipc": "9.2.1", "strip-ansi": "^6.0.0", @@ -133,7 +133,6 @@ "@jest/reporters": "^29.3.1", "@jest/types": "^29.3.1", "@types/bunyan": "^1.8.8", - "@types/glob": "^8.0.0", "@types/jest": "^29.2.5", "@types/lodash": "^4.14.191", "@types/lodash.get": "^4.4.7", @@ -157,6 +156,7 @@ "eslint-plugin-node": "^11.1.0", "eslint-plugin-prettier": "^5.0.0", "eslint-plugin-unicorn": "^48.0.1", + "globby":"^11.1.0", "http-server": "^14.1.1", "husky": "^8.0.3", "is-ci": "^3.0.1", diff --git a/src/index.ts b/src/index.ts index 5a14352..16dd428 100644 --- a/src/index.ts +++ b/src/index.ts @@ -54,6 +54,13 @@ export const $Unshift = realm.metadataDSL.$Unshift; */ export const $Assign = realm.metadataDSL.$Assign; +/** + * Pseudo-annotation that allows to associate metadata with a test block. + * It is not an ECMAScript decorator, but it behaves similarly. + * Use it to ensure multiple placeholder values to an object in metadata. + */ +export const $Defaults = realm.metadataDSL.$Defaults; + /** * Pseudo-annotation that allows to associate metadata with a test block. * It is not an ECMAScript decorator, but it behaves similarly. diff --git a/src/metadata/__tests__/__snapshots__/integration.test.ts.snap b/src/metadata/__tests__/__snapshots__/integration.test.ts.snap index f792aef..1fa9df9 100644 --- a/src/metadata/__tests__/__snapshots__/integration.test.ts.snap +++ b/src/metadata/__tests__/__snapshots__/integration.test.ts.snap @@ -568,7 +568,7 @@ tests_default_hook_nesting_js_hook_0 : id = "hook_0" tests_default_hook_nesting_js_hook_0 : hookType = "beforeEach" object "DescribeBlockMetadata" as tests_default_hook_nesting_js_describe_1 #ded tests_default_hook_nesting_js_describe_1 : id = "describe_1" -tests_default_hook_nesting_js_describe_1 : data = {\\n "vendor": {\\n "maintainer": {\\n "name": "Jane Smith",\\n "email": "jane.smith@example.com"\\n },\\n "lead": {\\n "name": "Samantha Jones",\\n "email": "samantha.jones@example.com"\\n },\\n "description": "This is a sample test suite."\\n }\\n} +tests_default_hook_nesting_js_describe_1 : data = {\\n "vendor": {\\n "maintainer": {\\n "name": "Jane Smith",\\n "email": "jane.smith@example.com"\\n },\\n "lead": {\\n "name": "Samantha Jones",\\n "email": "samantha.jones@example.com"\\n },\\n "author": {\\n "name": "John Doe",\\n "email": "john.doe@example.com"\\n },\\n "description": "This is a sample test suite."\\n }\\n} object "HookDefinitionMetadata" as tests_default_hook_nesting_js_hook_1 #fdd tests_default_hook_nesting_js_hook_1 : id = "hook_1" tests_default_hook_nesting_js_hook_1 : data = {\\n "vendor": {\\n "description": "Prepare the environment"\\n }\\n} @@ -1562,7 +1562,7 @@ tests_default_hook_nesting_js_hook_0 : id = "hook_0" tests_default_hook_nesting_js_hook_0 : hookType = "beforeEach" object "DescribeBlockMetadata" as tests_default_hook_nesting_js_describe_1 #ded tests_default_hook_nesting_js_describe_1 : id = "describe_1" -tests_default_hook_nesting_js_describe_1 : data = {\\n "vendor": {\\n "maintainer": {\\n "name": "Jane Smith",\\n "email": "jane.smith@example.com"\\n },\\n "lead": {\\n "name": "Samantha Jones",\\n "email": "samantha.jones@example.com"\\n },\\n "description": "This is a sample test suite."\\n }\\n} +tests_default_hook_nesting_js_describe_1 : data = {\\n "vendor": {\\n "maintainer": {\\n "name": "Jane Smith",\\n "email": "jane.smith@example.com"\\n },\\n "lead": {\\n "name": "Samantha Jones",\\n "email": "samantha.jones@example.com"\\n },\\n "author": {\\n "name": "John Doe",\\n "email": "john.doe@example.com"\\n },\\n "description": "This is a sample test suite."\\n }\\n} object "HookDefinitionMetadata" as tests_default_hook_nesting_js_hook_1 #fdd tests_default_hook_nesting_js_hook_1 : id = "hook_1" tests_default_hook_nesting_js_hook_1 : data = {\\n "vendor": {\\n "description": "Prepare the environment"\\n }\\n} @@ -2536,7 +2536,7 @@ tests_default_hook_nesting_js_hook_0 : id = "hook_0" tests_default_hook_nesting_js_hook_0 : hookType = "beforeEach" object "DescribeBlockMetadata" as tests_default_hook_nesting_js_describe_1 #ded tests_default_hook_nesting_js_describe_1 : id = "describe_1" -tests_default_hook_nesting_js_describe_1 : data = {\\n "vendor": {\\n "maintainer": {\\n "name": "Jane Smith",\\n "email": "jane.smith@example.com"\\n },\\n "lead": {\\n "name": "Samantha Jones",\\n "email": "samantha.jones@example.com"\\n },\\n "description": "This is a sample test suite."\\n }\\n} +tests_default_hook_nesting_js_describe_1 : data = {\\n "vendor": {\\n "maintainer": {\\n "name": "Jane Smith",\\n "email": "jane.smith@example.com"\\n },\\n "lead": {\\n "name": "Samantha Jones",\\n "email": "samantha.jones@example.com"\\n },\\n "author": {\\n "name": "John Doe",\\n "email": "john.doe@example.com"\\n },\\n "description": "This is a sample test suite."\\n }\\n} object "HookDefinitionMetadata" as tests_default_hook_nesting_js_hook_1 #fdd tests_default_hook_nesting_js_hook_1 : id = "hook_1" tests_default_hook_nesting_js_hook_1 : data = {\\n "vendor": {\\n "description": "Prepare the environment"\\n }\\n} diff --git a/src/metadata/containers/BaseMetadata.ts b/src/metadata/containers/BaseMetadata.ts index 44f927a..c1a5c8a 100644 --- a/src/metadata/containers/BaseMetadata.ts +++ b/src/metadata/containers/BaseMetadata.ts @@ -78,6 +78,30 @@ export abstract class BaseMetadata implements Metadata { return this; } + defaults(path: undefined | string | readonly string[], value: object): this { + const oldValue = this.#get(path, {}); + const source = (oldValue && typeof oldValue === 'object' ? oldValue : {}) as Data; + for (const key of Object.keys(value)) { + if (source[key] === undefined) { + source[key] = (value as Data)[key]; + } + } + if (path != null) { + this.#set(path, source); + } + + this[symbols.context].emitter.emit({ + type: 'write_metadata', + testFilePath: this[symbols.id].testFilePath, + targetId: this[symbols.id].identifier, + path, + value, + operation: 'defaults', + }); + + return this; + } + merge(path: undefined | string | readonly string[], value: object): this { const oldValue = this.#get(path, {}); const source = oldValue && typeof oldValue === 'object' ? oldValue : {}; diff --git a/src/metadata/dsl/MetadataDSL.ts b/src/metadata/dsl/MetadataDSL.ts index 358eb81..fd726c6 100644 --- a/src/metadata/dsl/MetadataDSL.ts +++ b/src/metadata/dsl/MetadataDSL.ts @@ -75,6 +75,15 @@ export class MetadataDSL { }); }; + $Defaults = (path: string | readonly string[] | undefined, value: Data): void => { + this.#assertPath(path); + this.#assertValue(value); + + this.schedule(() => { + this.#metadata().defaults(path, value); + }); + }; + $Merge = (path: string | readonly string[] | undefined, value: Data): void => { this.#assertPath(path); this.#assertValue(value); diff --git a/src/metadata/events/WriteMetadataEvent.ts b/src/metadata/events/WriteMetadataEvent.ts index c7ac1fe..1254b01 100644 --- a/src/metadata/events/WriteMetadataEvent.ts +++ b/src/metadata/events/WriteMetadataEvent.ts @@ -4,5 +4,5 @@ export type WriteMetadataEvent = { targetId: string; // instance ID path?: string | readonly string[]; value: unknown; - operation: 'set' | 'assign' | 'merge' | 'push' | 'unshift'; + operation: 'set' | 'assign' | 'defaults' | 'merge' | 'push' | 'unshift'; }; diff --git a/src/metadata/types/Metadata.ts b/src/metadata/types/Metadata.ts index 0172485..e3c3a27 100644 --- a/src/metadata/types/Metadata.ts +++ b/src/metadata/types/Metadata.ts @@ -2,12 +2,13 @@ import type { Data } from './Data'; export interface Metadata { readonly id: string; - get(): Readonly; - get(path?: string | readonly string[], fallbackValue?: T): T; + get(): Readonly; + get(path?: string | readonly string[], fallbackValue?: T): Readonly; set(path: string | readonly string[], value: unknown): this; push(path: string | readonly string[], values: unknown[]): this; unshift(path: string | readonly string[], values: unknown[]): this; assign(path: undefined | string | readonly string[], value: Data): this; + defaults(path: undefined | string | readonly string[], value: Data): this; merge(path: undefined | string | readonly string[], value: Data): this; }