@jsenv/snapshot 2.9.4 → 2.9.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/readme.md +149 -0
- package/src/replace_fluctuating_values.js +11 -5
- package/README.md +0 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jsenv/snapshot",
|
|
3
|
-
"version": "2.9.
|
|
3
|
+
"version": "2.9.5",
|
|
4
4
|
"description": "Snapshot testing",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"@jsenv/assert": "4.4.0",
|
|
37
37
|
"@jsenv/ast": "6.2.16",
|
|
38
|
-
"@jsenv/exception": "1.1.
|
|
38
|
+
"@jsenv/exception": "1.1.1",
|
|
39
39
|
"@jsenv/filesystem": "4.10.0",
|
|
40
40
|
"@jsenv/terminal-recorder": "1.4.4",
|
|
41
41
|
"@jsenv/urls": "2.5.2",
|
package/readme.md
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# snapshot
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@jsenv/snapshot)
|
|
4
|
+
|
|
5
|
+
A tool to generate snapshots during tests.
|
|
6
|
+
|
|
7
|
+
## A word on snapshot testing
|
|
8
|
+
|
|
9
|
+
Snapshot testing consists into:
|
|
10
|
+
|
|
11
|
+
1. Making code execution produce file(s). They are called snapshots.
|
|
12
|
+
2. Make further code execution follow these steps:
|
|
13
|
+
1. Read existing snapshot
|
|
14
|
+
2. Execute code
|
|
15
|
+
3. Read new snapshot
|
|
16
|
+
4. Compare the two snapshots and throw if there is a diff
|
|
17
|
+
|
|
18
|
+
This force code execution to produce the same snapshots. Meaning that code being tested still behave as expected.
|
|
19
|
+
|
|
20
|
+
## How it works
|
|
21
|
+
|
|
22
|
+
`@jsenv/snapshot` behaves as follow:
|
|
23
|
+
|
|
24
|
+
When there is no snapshot(s), the snapshot won't be compared. It happens the very first time you generate snapshots or because all snapshot files have been removed for some reason.
|
|
25
|
+
|
|
26
|
+
For all next code executions, snapshots are compared and
|
|
27
|
+
|
|
28
|
+
- An error is thrown when `process.env.CI` is set (code is executed in CI).
|
|
29
|
+
- Otherwise nothing special happens (it's your job to review eventual diff in the snapshots, using `git diff` for example)
|
|
30
|
+
|
|
31
|
+
> Every function accepts a `throwWhenDiff` param to throw even if runned locally. You can also set `process.env.CI` before executing your code.
|
|
32
|
+
|
|
33
|
+
## takeFileSnapshot(fileUrl)
|
|
34
|
+
|
|
35
|
+
The code below ensure `writeFileTxt` write `content` into "./file.txt".
|
|
36
|
+
Changing that behaviour would fail snapshot comparison.
|
|
37
|
+
|
|
38
|
+
```js
|
|
39
|
+
import { writeFileSync } from "node:fs";
|
|
40
|
+
import { takeFileSnapshot } from "@jsenv/snapshot";
|
|
41
|
+
|
|
42
|
+
const fileTxtUrl = new URL("./file.txt", import.meta.url);
|
|
43
|
+
const writeFileTxt = (content) => {
|
|
44
|
+
writeFileSync(writeFileTxt, content);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// take snapshot of "./file.txt"
|
|
48
|
+
const fileSnapshot = takeFileSnapshot(fileTxtUrl);
|
|
49
|
+
writeFileTxt("Hello world");
|
|
50
|
+
// compare the state of "./file.txt" with previous version
|
|
51
|
+
fileSnapshot.compare();
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## takeDirectorySnapshot(directoryUrl)
|
|
55
|
+
|
|
56
|
+
The code below ensure `writeManyFiles` always write twos file: "./dir/a.txt" and "./dir/b.txt" with the content "a" and "b".
|
|
57
|
+
Changing that behaviour would fail snapshot comparison.
|
|
58
|
+
|
|
59
|
+
```js
|
|
60
|
+
import { writeFileSync } from "node:fs";
|
|
61
|
+
import { takeDirectorySnapshot } from "@jsenv/snapshot";
|
|
62
|
+
|
|
63
|
+
const directoryUrl = new URL("./dir/", import.meta.url);
|
|
64
|
+
const writeManyFiles = () => {
|
|
65
|
+
writeFileSync(new URL("./a.txt", directoryUrl), "a");
|
|
66
|
+
writeFileSync(new URL("./b.txt", directoryUrl), "b");
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// take snapshot of "./dir/"
|
|
70
|
+
const directorySnapshot = takeDirectorySnapshot(directoryUrl);
|
|
71
|
+
writeFileTxt(directoryUrl);
|
|
72
|
+
// compare the state of "./dir/" with previous version
|
|
73
|
+
directorySnapshot.compare();
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## snapshotTests(testFileUrl, fnRegistertingTests, options)
|
|
77
|
+
|
|
78
|
+
This function is wonderful:
|
|
79
|
+
|
|
80
|
+
```js
|
|
81
|
+
import { snapshotTests } from "@jsenv/snapshot";
|
|
82
|
+
|
|
83
|
+
const getCircleArea = (circleRadius) => {
|
|
84
|
+
if (isNaN(circleRadius)) {
|
|
85
|
+
throw new TypeError(
|
|
86
|
+
`circleRadius must be a number, received ${circleRadius}`,
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
return circleRadius * circleRadius * Math.PI;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
await snapshotTests(import.meta.url, ({ test }) => {
|
|
93
|
+
test("when radius is 2", () => {
|
|
94
|
+
return getCircleArea(2);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("when radius is 10", () => {
|
|
98
|
+
return getCircleArea(10);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test("when radius is null", () => {
|
|
102
|
+
return getCircleArea(null);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
The code above is executing `getCircleArea` and produces a markdown files describing how it goes.
|
|
108
|
+
This markdown will be compared with any previous version ensuring `getCircleArea` still behave as expected.
|
|
109
|
+
|
|
110
|
+
See the markdown at [./docs/\_circle_area.test.js/circle_area.test.js.md](./docs/_circle_area.test.js/circle_area.test.js.md)
|
|
111
|
+
|
|
112
|
+
Why is it so wonderful?
|
|
113
|
+
|
|
114
|
+
- You don't have to assert anything, you just call the function
|
|
115
|
+
- The markdown files can be reviewed to ensure it is what you expect
|
|
116
|
+
- The markdown files can be used as documentation
|
|
117
|
+
- Changes in the source code would be reflected in the markdown making it easy to review
|
|
118
|
+
|
|
119
|
+
There is a few more very helpul things hapenning:
|
|
120
|
+
|
|
121
|
+
- Log side effects are catched, see [./docs/\_logs.test.js/log.test.js.md](./docs/_log.test.js/log.test.js.md)
|
|
122
|
+
- Filesystem side effects are catched and undone, see [./docs/\_filesystem.test.js/filesystem.test.js.md](./docs/_filesystem.test.js/filesystem.test.js.md)
|
|
123
|
+
- Fluctuating values are replaced with stable values, see [#Fluctuating values replacement](#fluctuating-values-replacement)
|
|
124
|
+
|
|
125
|
+
Advanced examples:
|
|
126
|
+
|
|
127
|
+
- Tests how `assert` throw in many different ways: [@jsenv/assert/tests/array.test.js.md](../assert/tests/_array.test.js/array.test.js.md).
|
|
128
|
+
- Tests generating build files, starting a server and executing build files in a browser: [@jsenv/core/tests/script_type_module_basic.test.mjs](../../../tests/build/basics/script_type_module_basic/_script_type_module_basic.test.mjs/script_type_module_basic.test.mjs.md).
|
|
129
|
+
|
|
130
|
+
### Fluctuating values replacement
|
|
131
|
+
|
|
132
|
+
Snapshots will be generated on your machine or the machine of an other contributor, then on the CI.
|
|
133
|
+
|
|
134
|
+
Each execution will happen in a different context. This context influence behaviour of the code and as a consequence might change the snapshot being generated.
|
|
135
|
+
|
|
136
|
+
- time
|
|
137
|
+
- operating system
|
|
138
|
+
- filesystem location
|
|
139
|
+
- available ressources,
|
|
140
|
+
- and so on...
|
|
141
|
+
|
|
142
|
+
To ensure the snapshot generated is not influenced, all fluctuating values are replaced with stable values.
|
|
143
|
+
|
|
144
|
+
- Things like "2s" becomes "Xs"
|
|
145
|
+
- Filesystem urls dynamic parts are replaced
|
|
146
|
+
- Port in https urls is removed
|
|
147
|
+
- and so on...
|
|
148
|
+
|
|
149
|
+
If something is fluctuating and makes your snapshot testing unstable, you can open an issue or create a pull request.
|
|
@@ -68,7 +68,7 @@ export const replaceFluctuatingValues = (
|
|
|
68
68
|
// );
|
|
69
69
|
return string;
|
|
70
70
|
};
|
|
71
|
-
const replaceThings = (string) => {
|
|
71
|
+
const replaceThings = (string, { shouldReplaceDurations } = {}) => {
|
|
72
72
|
if (stringType === "filesystem") {
|
|
73
73
|
return replaceFilesystemWellKnownValues(string);
|
|
74
74
|
}
|
|
@@ -79,7 +79,9 @@ export const replaceFluctuatingValues = (
|
|
|
79
79
|
willBeWrittenOnFilesystem: false,
|
|
80
80
|
});
|
|
81
81
|
string = replaceHttpUrls(string);
|
|
82
|
-
|
|
82
|
+
if (shouldReplaceDurations !== false) {
|
|
83
|
+
string = replaceDurations(string);
|
|
84
|
+
}
|
|
83
85
|
string = replaceSizes(string);
|
|
84
86
|
return string;
|
|
85
87
|
};
|
|
@@ -113,13 +115,17 @@ export const replaceFluctuatingValues = (
|
|
|
113
115
|
if (attributes) {
|
|
114
116
|
for (const name of Object.keys(attributes)) {
|
|
115
117
|
const attributeValue = attributes[name];
|
|
118
|
+
let newValue;
|
|
116
119
|
if (name === "timestamp") {
|
|
117
|
-
|
|
120
|
+
newValue = "[timestamp]";
|
|
118
121
|
} else if (name === "time") {
|
|
119
|
-
|
|
122
|
+
newValue = "[time]";
|
|
120
123
|
} else {
|
|
121
|
-
|
|
124
|
+
newValue = replaceThings(attributeValue, {
|
|
125
|
+
shouldReplaceDurations: name !== "style",
|
|
126
|
+
});
|
|
122
127
|
}
|
|
128
|
+
attributes[name] = newValue;
|
|
123
129
|
}
|
|
124
130
|
setHtmlNodeAttributes(node, attributes);
|
|
125
131
|
}
|