Skip to content

Commit 084897f

Browse files
committed
Simplify handling of internal values a bit
The weak map that I previously talked about [1] wasn't as I intended it to be, in this case the map only ever contain a single item. My intention was to keep a map outside of the "test's runtime", as I am doing with this change, but I now realize that strings aren't legal WeakMap keys. Furthremore, I like that the reference contained in the Cypress environment is a primitive value. Less surprises this way. Lastly, because the value known to Cypress is a primitive value and not something where we're messing with method overloading, I am more comfortable with reomving the opt-out feature of this. [1] #908 (comment)
1 parent 0eadc28 commit 084897f

File tree

4 files changed

+51
-81
lines changed

4 files changed

+51
-81
lines changed

features/issues/908.feature

Lines changed: 6 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -6,40 +6,18 @@ Feature: hide internals from cypress environment
66
"""
77
Feature: a feature
88
Scenario: hide internal state by default
9-
* the internal state should not be visible
10-
11-
@reportInternalCucumberState
12-
Scenario: expose internal state with tag
13-
* the internal state should be visible
9+
Then the visible internal state should be a mere reference
1410
"""
1511
And a file named "cypress/support/step_definitions/steps.js" with:
1612
"""
17-
const { inspect } = require('util');
1813
const { Then } = require("@badeball/cypress-cucumber-preprocessor");
1914
const { INTERNAL_SPEC_PROPERTIES } = require("@badeball/cypress-cucumber-preprocessor/lib/constants");
20-
21-
Then("the internal state should not be visible", () => {
15+
// From https://github.com/rfrench/chai-uuid/blob/master/index.js.
16+
const UUID_V4_EXPR = /^[0-9A-F]{8}-[0-9A-F]{4}-[4][0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i;
17+
Then("the visible internal state should be a mere reference", () => {
2218
const properties = Cypress.env(INTERNAL_SPEC_PROPERTIES);
23-
const serializedProperties = JSON.stringify(properties);
24-
const inspectedProperties = inspect(properties, {depth: 0});
25-
26-
expect(properties).not.to.be.undefined;
27-
expect(properties.pickle).to.be.undefined;
28-
expect(serializedProperties).to.be.undefined;
29-
expect(inspectedProperties).to.be.equal('{}');
30-
});
31-
32-
Then("the internal state should be visible", () => {
33-
const properties = Cypress.env(INTERNAL_SPEC_PROPERTIES);
34-
const serializedProperties = JSON.stringify(properties);
35-
const inspectedProperties = inspect(properties, {depth: 0});
36-
37-
expect(properties).not.to.be.undefined;
38-
expect(properties.pickle).not.to.be.undefined;
39-
expect(serializedProperties).not.to.be.undefined;
40-
// Must be more than an empty object, cannot test the exact string because
41-
// it contains random values in a random order.
42-
expect(inspectedProperties).to.have.length.of.at.least(3);
19+
expect(properties).to.be.a("string");
20+
expect(properties).to.match(UUID_V4_EXPR);
4321
});
4422
"""
4523
When I run cypress

lib/assertions.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,16 @@ import { isString } from "./type-guards";
22

33
import { homepage } from "../package.json";
44

5-
export function fail(message: string) {
6-
throw new Error(
5+
export function createError(message: string) {
6+
return new Error(
77
`${message} (this might be a bug, please report at ${homepage})`
88
);
99
}
1010

11+
export function fail(message: string) {
12+
throw createError(message);
13+
}
14+
1115
export function assert(value: unknown, message: string): asserts value {
1216
if (value != null) {
1317
return;

lib/create-tests.ts

Lines changed: 24 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -114,48 +114,29 @@ export interface InternalSuiteProperties {
114114
isEventHandlersAttached?: boolean;
115115
}
116116

117-
class InternalSpecPropertiesReference {
118-
private static referenceMap = new WeakMap<
119-
InternalSpecPropertiesReference,
120-
InternalSpecProperties
121-
>();
122-
123-
private constructor() {
124-
// Empty constructor just to set private visibility
125-
}
126-
127-
public static new(
128-
scenarioName: string,
129-
properties: InternalSpecProperties
130-
): InternalSpecPropertiesReference {
131-
const reference = new this();
132-
133-
this.referenceMap.set(reference, properties);
134-
135-
return reference;
136-
}
137-
138-
public dereference(): InternalSpecProperties {
139-
// Non-null assertion is safe because an instance of reference is only given out after it is included
140-
// in the reference map.
141-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
142-
return InternalSpecPropertiesReference.referenceMap.get(this)!;
143-
}
144-
145-
public toJSON(): void {
146-
// Noop override to hide the reference from serialization.
147-
// Any property pointing to a reference will be ignored by JSON.stringify
148-
}
117+
const internalSpecProperties = new Map<string, InternalSpecProperties>();
118+
119+
function createInternalSpecProperties(
120+
properties: InternalSpecProperties
121+
): string {
122+
const reference = uuid();
123+
internalSpecProperties.set(reference, properties);
124+
return reference;
149125
}
150126

151127
export function retrieveInternalSpecProperties(): InternalSpecProperties {
152-
const envValue = Cypress.env(INTERNAL_SPEC_PROPERTIES);
128+
const reference = Cypress.env(INTERNAL_SPEC_PROPERTIES) as string;
153129

154-
if (envValue instanceof InternalSpecPropertiesReference) {
155-
return envValue.dereference();
156-
}
130+
return assertAndReturn(
131+
internalSpecProperties.get(reference),
132+
`Expected to find internal spec properties with reference = ${reference}`
133+
);
134+
}
157135

158-
return envValue;
136+
function updateInternalSpecProperties(
137+
newProperties: Partial<InternalSpecProperties>
138+
): void {
139+
Object.assign(retrieveInternalSpecProperties(), newProperties);
159140
}
160141

161142
function retrieveInternalSuiteProperties():
@@ -361,9 +342,8 @@ function createPickle(
361342
};
362343

363344
const internalEnv = {
364-
[INTERNAL_SPEC_PROPERTIES]: tags.includes("@reportInternalCucumberState")
365-
? internalProperties
366-
: InternalSpecPropertiesReference.new(scenarioName, internalProperties),
345+
[INTERNAL_SPEC_PROPERTIES]:
346+
createInternalSpecProperties(internalProperties),
367347
};
368348

369349
const suiteOptions = tags
@@ -819,8 +799,10 @@ export default function createTests(
819799
/**
820800
* Repopulate internal properties in case previous test is retried.
821801
*/
822-
properties.testCaseStartedId = uuid();
823-
properties.remainingSteps = [...properties.allSteps];
802+
updateInternalSpecProperties({
803+
testCaseStartedId: uuid(),
804+
remainingSteps: [...properties.allSteps],
805+
});
824806
});
825807

826808
after(function () {

lib/methods.ts

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
1+
import { Pickle } from "@cucumber/messages";
2+
13
import parse from "@cucumber/tag-expressions";
4+
25
import { fromByteArray } from "base64-js";
3-
import { assertAndReturn } from "./assertions";
6+
7+
import { createError } from "./assertions";
8+
49
import { collectTagNames } from "./ast-helpers";
510

611
import {
712
INTERNAL_SPEC_PROPERTIES,
813
TASK_CREATE_STRING_ATTACHMENT,
914
} from "./constants";
10-
import {
11-
InternalSpecProperties,
12-
retrieveInternalSpecProperties,
13-
} from "./create-tests";
15+
16+
import { retrieveInternalSpecProperties } from "./create-tests";
1417

1518
import { runStepWithLogGroup } from "./cypress";
1619

@@ -123,10 +126,13 @@ export const NOT_FEATURE_ERROR =
123126
"Expected to find internal properties, but didn't. This is likely because you're calling doesFeatureMatch() in a non-feature spec. Use doesFeatureMatch() in combination with isFeature() if you have both feature and non-feature specs";
124127

125128
function doesFeatureMatch(expression: string) {
126-
const { pickle } = assertAndReturn(
127-
retrieveInternalSpecProperties(),
128-
NOT_FEATURE_ERROR
129-
) as InternalSpecProperties;
129+
let pickle: Pickle;
130+
131+
try {
132+
pickle = retrieveInternalSpecProperties().pickle;
133+
} catch {
134+
throw createError(NOT_FEATURE_ERROR);
135+
}
130136

131137
return parse(expression).evaluate(collectTagNames(pickle.tags));
132138
}

0 commit comments

Comments
 (0)