31
31
* `;
32
32
* ```
33
33
*
34
- * Calling `assertSnapshot` in a test will throw an `AssertionError`, causing the
35
- * test to fail, if the snapshot created during the test does not match the one in
36
- * the snapshot file.
34
+ * The `assertInlineSnapshot` function will create a snapshot of a value and compare it
35
+ * to a reference snapshot, which is stored in the test file.
36
+ *
37
+ * ```ts
38
+ * // example_test.ts
39
+ * import { assertInlineSnapshot } from "@std/testing/snapshot";
40
+ *
41
+ * Deno.test("isInlineSnapshotMatch", async function (t): Promise<void> {
42
+ * const a = {
43
+ * hello: "world!",
44
+ * example: 123,
45
+ * };
46
+ * await assertInlineSnapshot(
47
+ * t,
48
+ * a,
49
+ * `{
50
+ * hello: "world!",
51
+ * example: 123,
52
+ * }`
53
+ * );
54
+ * });
55
+ * ```
56
+ *
57
+ * If the snapshot of the passed `actual` does not match the expected snapshot,
58
+ * `assertSnapshot` and `assetInlineSnapshot` will throw an `AssertionError`,
59
+ * causing the test to fail.
37
60
*
38
61
* ## Updating Snapshots:
39
62
*
42
65
* by running the snapshot tests in update mode. Tests can be run in update mode by
43
66
* passing the `--update` or `-u` flag as an argument when running the test. When
44
67
* this flag is passed, then any snapshots which do not match will be updated.
68
+ * When this flag is not passed, tests missing snapshots will fail.
45
69
*
46
70
* ```sh
47
71
* deno test --allow-all -- --update
48
72
* ```
49
73
*
50
- * Additionally, new snapshots will only be created when this flag is present.
74
+ * For inline snapshots, using an `expectedSnapshot` of the template literal
75
+ * \`CREATE\` will mark a snapshot for creation. This template literal must not
76
+ * appear elsewhere in the file, as the updater uses it to determine where to
77
+ * place new snapshots.
78
+ *
79
+ * ```ts
80
+ * // example_test.ts
81
+ * import { assertInlineSnapshot } from "@std/testing/snapshot";
82
+ *
83
+ * Deno.test("isInlineSnapshotMatch", async function (t): Promise<void> {
84
+ * const a = {
85
+ * hello: "world!",
86
+ * example: 123,
87
+ * };
88
+ * await assertInlineSnapshot(t, a, `UPDATE`);
89
+ * });
90
+ * ```
91
+ *
92
+ * Inline snapshots do not use the update flag.
93
+ *
94
+ * ```sh
95
+ * deno test --allow-all
96
+ * ```
51
97
*
52
98
* ## Permissions:
53
99
*
54
- * When running snapshot tests , the `--allow-read` permission must be enabled, or
100
+ * When running `assertSnapshot` , the `--allow-read` permission must be enabled, or
55
101
* else any calls to `assertSnapshot` will fail due to insufficient permissions.
56
102
* Additionally, when updating snapshots, the `--allow-write` permission must also
57
103
* be enabled, as this is required in order to update snapshot files.
60
106
* snapshot files. As such, the allow list for `--allow-read` and `--allow-write`
61
107
* can be limited to only include existing snapshot files, if so desired.
62
108
*
109
+ * If no snapshots are created, `assertInlineSnapshot` does not require any
110
+ * permissions. However, creating snapshots requires `--allow-read` and
111
+ * `--allow-write` on any test files for which new snapshots will be added.
112
+ * Additionally, `--allow-run` is required if any files will be formatted (which is
113
+ * the default if not specified in the options).
114
+ *
63
115
* ## Options:
64
116
*
65
- * The `assertSnapshot` function optionally accepts an options object.
117
+ * The `assertSnapshot` and `assertInlineSnapshot` functions optionally accept an
118
+ * options object.
66
119
*
67
120
* ```ts
68
121
* // example_test.ts
79
132
* });
80
133
* ```
81
134
*
82
- * You can also configure default options for `assertSnapshot`.
135
+ * You can also configure default options for `assertSnapshot` and `assertInlineSnapshot` .
83
136
*
84
137
* ```ts
85
138
* // example_test.ts
86
- * import { createAssertSnapshot } from "@std/testing/snapshot";
139
+ * import { createAssertSnapshot, createAssertInlineSnapshot } from "@std/testing/snapshot";
87
140
*
88
141
* const assertSnapshot = createAssertSnapshot({
89
142
* // options
90
143
* });
144
+ * const assertInlineSnapshot = createAssertInlineSnapshot({
145
+ * // options
146
+ * });
91
147
* ```
92
148
*
93
- * When configuring default options like this, the resulting `assertSnapshot`
94
- * function will function the same as the default function exported from the
95
- * snapshot module. If passed an optional options object, this will take precedence
149
+ * When configuring default options like this, the resulting `assertSnapshot` or
150
+ * `assertInlineSnapshot` function will function the same as the default function exported
151
+ * from thesnapshot module. If passed an optional options object, this will take precedence
96
152
* over the default options, where the value provided for an option differs.
97
153
*
98
- * It is possible to "extend" an `assertSnapshot` function which has been
99
- * configured with default options.
154
+ * It is possible to "extend" an `assertSnapshot` or `assertInlineSnapshot` function which
155
+ * has been configured with default options.
100
156
*
101
157
* ```ts
102
158
* // example_test.ts
@@ -196,13 +252,13 @@ export interface SnapshotOptions<T = unknown> {
196
252
197
253
/** The options for {@linkcode assertInlineSnapshot}. */
198
254
export interface InlineSnapshotOptions < T = unknown >
199
- extends Pick < SnapshotOptions , "msg" | "serializer" > {
255
+ extends Pick < SnapshotOptions < T > , "msg" | "serializer" > {
200
256
/**
201
257
* Whether to format the test file after updating.
202
258
*
203
- * The default is `true`. If multiple snapshot tests are defined and have
204
- * incompatible `format` options, the snapshots will be written, the file will
205
- * not be formatted, and all updated tests will fail .
259
+ * The default is `true`. If multiple snapshots will be created in one test file
260
+ * and the tests have incompatible `format` options, the snapshots will be written,
261
+ * the file will not be formatted, and we will throw .
206
262
*/
207
263
format ?: boolean ;
208
264
}
@@ -567,7 +623,7 @@ class AssertInlineSnapshotContext {
567
623
568
624
/**
569
625
* Returns an instance of `AssertInlineSnapshotContext`. This will be retrieved from
570
- * a cache if an instance was already created for a given snapshot file path.
626
+ * a cache if an instance was already created for a given test file path.
571
627
*/
572
628
static fromContext (
573
629
testContext : Deno . TestContext ,
@@ -590,25 +646,12 @@ class AssertInlineSnapshotContext {
590
646
#indexToSnapshot: string [ ] = [ ] ;
591
647
#snapshotsCreated = 0 ;
592
648
#testFileUrl: URL ;
593
- #format: boolean | undefined | "error" ;
649
+ #format: boolean | undefined | "error" = undefined ;
594
650
595
651
constructor ( testFileUrl : URL ) {
596
652
this . #testFileUrl = testFileUrl ;
597
653
}
598
654
599
- /**
600
- * Asserts that `this.#currentSnapshots` has been initialized and then returns it.
601
- *
602
- * Should only be called when `this.#currentSnapshots` has already been initialized.
603
- */
604
- #getCurrentSnapshotsInitialized( ) {
605
- assert (
606
- this . #indexToSnapshot,
607
- "Snapshot was not initialized. This is a bug in `assertInlineSnapshot`." ,
608
- ) ;
609
- return this . #indexToSnapshot;
610
- }
611
-
612
655
/**
613
656
* Write updates to the snapshot file and log statistics.
614
657
*/
@@ -647,6 +690,24 @@ class AssertInlineSnapshotContext {
647
690
648
691
Deno . writeTextFileSync ( testFilePath , result ) ;
649
692
693
+ if ( this . #format === undefined || this . #format === true ) {
694
+ const command = new Deno . Command ( Deno . execPath ( ) , {
695
+ args : [ "fmt" , testFilePath ] ,
696
+ } ) ;
697
+ const { stderr, success } = command . outputSync ( ) ;
698
+ if ( ! success ) {
699
+ throw new Error (
700
+ `assertInlineSnapshot errored while formatting ${ testFilePath } :\n${
701
+ new TextDecoder ( ) . decode ( stderr )
702
+ } `,
703
+ ) ;
704
+ }
705
+ } else if ( this . #format === "error" ) {
706
+ throw new Error (
707
+ "assertInlineSnapshot was called with incompatible format options. Snapshots were added but the file was not formatted." ,
708
+ ) ;
709
+ }
710
+
650
711
const created = this . #snapshotsCreated;
651
712
if ( created > 0 ) {
652
713
// deno-lint-ignore no-console
@@ -694,12 +755,14 @@ class AssertInlineSnapshotContext {
694
755
}
695
756
696
757
/**
697
- * Creates a snapshot by index. Updates will be written to the snapshot file when all
758
+ * Creates a snapshot by index. Updates will be written to the test file when all
698
759
* tests have run.
699
760
*/
700
- createSnapshot ( index : number , snapshot : string , format : boolean ) {
701
- const currentSnapshots = this . #getCurrentSnapshotsInitialized( ) ;
702
- currentSnapshots [ index ] = snapshot ;
761
+ createSnapshot ( index : number , snapshot : string , format : boolean | undefined ) {
762
+ this . #indexToSnapshot[ index ] = snapshot ;
763
+
764
+ if ( format === undefined ) format = true ;
765
+
703
766
if ( this . #format === undefined ) {
704
767
this . #format = format ;
705
768
} else if ( this . #format !== format ) {
@@ -871,8 +934,8 @@ export function createAssertSnapshot<T>(
871
934
}
872
935
873
936
/**
874
- * Make an assertion that `actual` matches a snapshot . If the snapshot and `actual` do
875
- * not match, then throw.
937
+ * Make an assertion that `actual` matches `expectedSnapshot` . If they do not match,
938
+ * then throw.
876
939
*
877
940
* Type parameter can be specified to ensure values under comparison have the same type.
878
941
*
@@ -881,13 +944,13 @@ export function createAssertSnapshot<T>(
881
944
* import { assertInlineSnapshot } from "@std/testing/snapshot";
882
945
*
883
946
* Deno.test("snapshot", async (t) => {
884
- * await assertInlineSnapshot<number>(t, 2, "2" );
947
+ * await assertInlineSnapshot<number>(t, 2, `2` );
885
948
* });
886
949
* ```
887
950
* @typeParam T The type of the snapshot
888
951
* @param context The test context
889
952
* @param actual The actual value to compare
890
- * @param expectedSnapshot The expected snapshot, or `CREATE` to create
953
+ * @param expectedSnapshot The expected snapshot, or \ `CREATE\ ` to create
891
954
* @param options The options
892
955
*/
893
956
export async function assertInlineSnapshot < T > (
@@ -897,8 +960,8 @@ export async function assertInlineSnapshot<T>(
897
960
options ?: InlineSnapshotOptions < T > ,
898
961
) : Promise < void > ;
899
962
/**
900
- * Make an assertion that `actual` matches a snapshot . If the snapshot and `actual` do
901
- * not match, then throw.
963
+ * Make an assertion that `actual` matches `expectedSnapshot` . If they do not match,
964
+ * then throw.
902
965
*
903
966
* Type parameter can be specified to ensure values under comparison have the same type.
904
967
*
@@ -907,14 +970,13 @@ export async function assertInlineSnapshot<T>(
907
970
* import { assertInlineSnapshot } from "@std/testing/snapshot";
908
971
*
909
972
* Deno.test("snapshot", async (t) => {
910
- * await assertInlineSnapshot<number>(t, 2, "2" );
973
+ * await assertInlineSnapshot<number>(t, 2, `2` );
911
974
* });
912
975
* ```
913
- *
914
976
* @typeParam T The type of the snapshot
915
977
* @param context The test context
916
978
* @param actual The actual value to compare
917
- * @param expectedSnapshot The expected snapshot, or `CREATE` to create
979
+ * @param expectedSnapshot The expected snapshot, or \ `CREATE\ ` to create
918
980
* @param message The optional assertion message
919
981
*/
920
982
export async function assertInlineSnapshot < T > (
@@ -933,6 +995,7 @@ export async function assertInlineSnapshot(
933
995
934
996
const serializer = options . serializer ?? serialize ;
935
997
const actualSnapshot = serializer ( actual ) ;
998
+ // TODO(WWRS): dedent expectedSnapshot to allow snapshots to look nicer
936
999
937
1000
if ( expectedSnapshot === `CREATE` ) {
938
1001
const assertInlineSnapshotContext = AssertInlineSnapshotContext . fromContext (
@@ -943,11 +1006,62 @@ export async function assertInlineSnapshot(
943
1006
assertInlineSnapshotContext . createSnapshot (
944
1007
index ,
945
1008
actualSnapshot ,
946
- options . format ?? true ,
1009
+ options . format ,
947
1010
) ;
948
1011
} else if ( ! equal ( actualSnapshot , expectedSnapshot ) ) {
949
1012
throw new AssertionError (
950
1013
getSnapshotNotMatchMessage ( actualSnapshot , expectedSnapshot , options ) ,
951
1014
) ;
952
1015
}
953
1016
}
1017
+
1018
+ /**
1019
+ * Create {@linkcode assertInlineSnapshot} function with the given options.
1020
+ *
1021
+ * The specified option becomes the default for returned {@linkcode assertInlineSnapshot}
1022
+ *
1023
+ * @example Usage
1024
+ * ```ts
1025
+ * import { createAssertInlineSnapshot } from "@std/testing/snapshot";
1026
+ *
1027
+ * const assertInlineSnapshot = createAssertInlineSnapshot({
1028
+ * // Never format the test file after writing new snapshots
1029
+ * format: false
1030
+ * });
1031
+ *
1032
+ * Deno.test("a snapshot test case", async (t) => {
1033
+ * await assertInlineSnapshot(
1034
+ * t,
1035
+ * { foo: "Hello", bar: "World" },
1036
+ * `CREATE`
1037
+ * );
1038
+ * })
1039
+ * ```
1040
+ *
1041
+ * @typeParam T The type of the snapshot
1042
+ * @param options The options
1043
+ * @param baseAssertSnapshot {@linkcode assertInlineSnapshot } function implementation. Default to the original {@linkcode assertInlineSnapshot}
1044
+ * @returns {@linkcode assertInlineSnapshot } function with the given default options.
1045
+ */
1046
+ export function createAssertInlineSnapshot < T > (
1047
+ options : InlineSnapshotOptions < T > ,
1048
+ baseAssertSnapshot : typeof assertInlineSnapshot = assertInlineSnapshot ,
1049
+ ) : typeof assertInlineSnapshot {
1050
+ return async function (
1051
+ context : Deno . TestContext ,
1052
+ actual : T ,
1053
+ expectedSnapshot : string ,
1054
+ messageOrOptions ?: string | InlineSnapshotOptions < T > ,
1055
+ ) {
1056
+ const mergedOptions : InlineSnapshotOptions < T > = {
1057
+ ...options ,
1058
+ ...( typeof messageOrOptions === "string"
1059
+ ? {
1060
+ msg : messageOrOptions ,
1061
+ }
1062
+ : messageOrOptions ) ,
1063
+ } ;
1064
+
1065
+ await baseAssertSnapshot ( context , actual , expectedSnapshot , mergedOptions ) ;
1066
+ } ;
1067
+ }
0 commit comments