it("returns null if the file or folder does not exist", async () => {
    await fc.assert(
      fc.asyncProperty(
        arb.folderPath(true),
        arb.folderPath(),
        arb.fileName(),
        async (rootPath, filePath, folderPath) => {
          seedVolume(rootPath, [], []);
          const root = await FileDirectory.resolve(rootPath);

          await expect(root.entryType(filePath)).resolves.toEqual(null);
          await expect(root.entryType(folderPath)).resolves.toEqual(null);
        }
      )
    );
  })

Summary: Calling `entryType` on a path that does not exist should resolve to `null`.


unknown

it("lists files and folders within a directory", async () => {
    await fc.assert(
      fc.asyncProperty(
        arb.folderPath(true),
        fc.set(arb.fileName(3), { minLength: 5, maxLength: 10 }),
        async (rootPath, names) => {
          const mid = Math.min(names.length / 2);
          const folders = names.slice(0, mid);
          const files = names.slice(mid, names.length);
          seedVolume(rootPath, files, folders);

          const root = await FileDirectory.resolve(rootPath);

          for (const [name, type] of await root.list("./")) {
            if (type === "file") {
              expect(files.includes(name)).toBe(true);
            } else if (type === "folder") {
              expect(folders.includes(name)).toBe(true);
            }
          }
        }
      )
    );
  })

Summary: `FileDirectory.resolve(rootPath).list("./")` correctly identifies and lists files and folders within the seeded directory.


test('incrementRandom increments', () => {
	function uint8ArrayToNumber(arr: Uint8Array) {
		let num = 0n
		for (const i of arr) {
			num = num * 256n + BigInt(i)
		}
		return num
	}
	fc.assert(
		fc.property(
			fc.record({
				epoch: fc.integer({
					min: 0,
					max: epochMax,
				}),
				id: fc.uint8Array({
					minLength: idLength,
					maxLength: idLength,
				}),
			}),
			({ epoch, id }) => {
				prefixEpochToArray(epoch, id)
				const randomArrayA = id.slice(epochLength)
				incrementRandom(id)
				const randomArrayB = id.slice(epochLength)
				const actualEpoch = idToEpoch(id)
				assert.equal(actualEpoch, epoch)
				assert.equal(
					uint8ArrayToNumber(randomArrayA) + 1n,
					uint8ArrayToNumber(randomArrayB),
				)
			},
		),
	)
})

Summary: `incrementRandom` ensures the numeric value derived from a portion of a Uint8Array increases by one, while the epoch portion remains unchanged.


test('cursor/keyset pagination works for getNotes', async () => {
	const sortState = fc
		.tuple(
			fc.boolean().map((desc) => ({
				id: 'note.id' as const,
				desc: desc ? ('desc' as const) : undefined,
			})),
			fc.boolean().map((desc) => ({
				id: 'noteEdited' as const,
				desc: desc ? ('desc' as const) : undefined,
			})),
		)
		.chain((x) => fc.shuffledSubarray(x, { minLength: 1, maxLength: 1 }))
	const arbNum = fc.integer({ min: 0, max: 5 })
	const createdEditeds = fc.uniqueArray(
		fc
			.record({
				created: arbNum,
				edited: arbNum,
				rawId: fc.uint8Array({ maxLength: idLength, minLength: idLength }),
			})
			.map((x) => {
				const id = new Uint8Array(x.rawId)
				prefixEpochToArray(x.created, id)
				return {
					...x,
					rawId: id,
				}
			}),
		{
			minLength: 1,
			maxLength: 100,
			selector: (x) => x.rawId,
			comparator: uint8comparator,
		},
	)
	let lastDb: Database.Database | undefined
	await fc.assert(
		fc.asyncProperty(
			fc.record({
				createdEditeds,
				sortState,
			}),
			async ({ createdEditeds, sortState }) => {
				_kysely.resetSqlLog()
				const { database, remoteTemplateId } = await setupDb()
				const jsSorted: SimplifiedNote[] = []
				for (const { created, edited, rawId } of createdEditeds) {
					_binary.setRawId(() => rawId)
					const noteResponse = await insertNotes(userId, [
						{
							localId: base64urlId<NoteId>(),
							fieldValues: {},
							tags: [],
							remoteTemplateIds: [remoteTemplateId],
						},
					])
					const remoteNoteId = Array.from(noteResponse.values())[0]![0]
					const rawRemoteNoteId = base64urlToArray(remoteNoteId)
					jsSorted.push({ created, edited, remoteNoteId: rawRemoteNoteId })
					const hexNoteId = base16.encode(rawRemoteNoteId)
					database.exec(
						`UPDATE note SET edited = ${edited} WHERE id = x'${hexNoteId}'`,
					)
				}
				jsSorted.sort((a, b) => sort(a, b, sortState))

				_kysely.setPageSize(100_000_000)
				const sqlSorted = await getNotes(
					{
						nook,
						sortState,
						cursor: undefined,
					},
					userId,
				).then((x) => x.map(simplifyNote))

				// Act
				_kysely.setPageSize(3)
				const paginatedNotes = await getAllNotes(sortState)

				// Assert
				const actualNotes = paginatedNotes.map(simplifyNote)
				try {
					assert.sameDeepOrderedMembers(actualNotes, jsSorted)
					assert.sameDeepOrderedMembers(actualNotes, sqlSorted)
				} catch (e) {
					console.log('=================================================')
					console.log('sortState', sortState)
					console.log('actualNotes', actualNotes.map(prettier))
					console.log('jsSorted', jsSorted.map(prettier))
					console.log('sqlLog', _kysely.sqlLog.map(_kysely.prettierSqlLog))
					lastDb = database
					throw e
				}
			},
		),
	)
	if (lastDb != null) {
		// fs.writeFileSync('shrunken.db', lastDb.serialize())
	}
})

Summary: Cursor/keyset pagination in `getNotes` should produce results equivalent to sorting done in JavaScript and SQL orders.


test('created can be derived from id', async () => {
	const arbNum = fc.integer({ min: 0, max: 281474976710655 })
	const createdIds = fc.uniqueArray(
		fc
			.record({
				created: arbNum,
				rawId: fc.uint8Array({ maxLength: idLength, minLength: idLength }),
			})
			.map((x) => {
				const id = new Uint8Array(x.rawId)
				prefixEpochToArray(x.created, id)
				return {
					...x,
					rawId: id,
				}
			}),
		{
			minLength: 1,
			maxLength: 100,
			selector: (x) => x.rawId,
			comparator: uint8comparator,
		},
	)
	await fc.assert(
		fc.asyncProperty(
			fc.record({
				createdIds,
			}),
			async ({ createdIds }) => {
				const { database, remoteTemplateId } = await setupDb()
				for (const { created, rawId } of createdIds) {
					_binary.setRawId(() => rawId)
					const noteResponse = await insertNotes(userId, [
						{
							localId: base64urlId<NoteId>(),
							fieldValues: {},
							tags: [],
							remoteTemplateIds: [remoteTemplateId],
						},
					])
					const remoteNoteId = Array.from(noteResponse.values())[0]![0]
					const rawRemoteNoteId = base64urlToArray(remoteNoteId)

					const apiNote = await getNote(remoteNoteId, userId)
					const sqlNote = database
						.prepare(`SELECT created FROM note WHERE id = ?`)
						.get(rawRemoteNoteId) as { created: number }

					assert.equal(idToEpoch(rawRemoteNoteId), created)
					assert.equal(apiNote!.created.getTime(), created)
					assert.equal(sqlNote.created, created)
				}
			},
		),
	)
})

Summary: `created` timestamps should be correctly derived from IDs, verified by ensuring inserted notes reflect the expected creation time across API, database, and decoded ID values.


it("should parse CSV", () =>
    fc.assert(
      fc.asyncProperty(
        fc.gen().map((g) => {
          const header = g(FC.header, {
            // TextEncoderStream can't handle utf-16 string.
            fieldConstraints: {
              kindExcludes: ["string16bits"],
            },
          });
          const EOL = g(FC.eol);
          const csvData = g(FC.csvData, {
            // TextEncoderStream can't handle utf-16 string.
            fieldConstraints: {
              kindExcludes: ["string16bits"],
            },
            columnsConstraints: {
              minLength: header.length,
              maxLength: header.length,
            },
          });
          const EOF = g(fc.boolean);
          const csv = [
            header.map((v) => escapeField(v, { quote: true })).join(","),
            ...csvData.map((row) =>
              row.map((v) => escapeField(v, { quote: true })).join(","),
            ),
            ...(EOF ? [""] : []),
          ].join(EOL);
          const data =
            csvData.length >= 1
              ? csvData.map((row) =>
                  Object.fromEntries(row.map((v, i) => [header[i], v])),
                )
              : [];
          return {
            data,
            csv: new SingleValueReadableStream(csv).pipeThrough(
              new TextEncoderStream(),
            ),
          };
        }),
        async ({ data, csv }) => {
          let i = 0;
          for await (const row of parseUint8ArrayStream(csv)) {
            expect(data[i++]).toStrictEqual(row);
          }
        },
      ),
    ))

Summary: The function parses a CSV from a `Uint8Array` stream and verifies that the parsed rows match the expected data objects constructed from CSV inputs.


it("should parse CSV string to record of arrays", async () => {
    await fc.assert(
      fc.asyncProperty(
        fc.gen().map((g) => {
          const header = g(FC.header, {
            fieldConstraints: {
              kindExcludes: ["string16bits"],
            },
          });
          const EOL = g(FC.eol);
          const csvData = g(FC.csvData, {
            fieldConstraints: {
              kindExcludes: ["string16bits"],
            },
            columnsConstraints: {
              minLength: header.length,
              maxLength: header.length,
            },
          });
          const EOF = g(fc.boolean);
          const csv = [
            header.map((v) => escapeField(v, { quote: true })).join(","),
            ...csvData.map((row) =>
              row.map((v) => escapeField(v, { quote: true })).join(","),
            ),
            ...(EOF ? [""] : []),
          ].join(EOL);
          const data =
            csvData.length >= 1
              ? csvData.map((row) =>
                  Object.fromEntries(row.map((v, i) => [header[i], v])),
                )
              : [];
          return { data, csv, header };
        }),
        async ({ data, csv }) => {
          let i = 0;
          const result = parseStringToArraySyncWASM(csv);
          expect(result.length).toEqual(data.length);
          for (const record of result) {
            expect(data[i++]).toEqual(record);
          }
        },
      ),
    );
  })

Summary: Parsing a CSV string into a record of arrays should match the expected data length and content.


it("should parse CSV", () =>
    fc.assert(
      fc.asyncProperty(
        fc.gen().map((g) => {
          const header = g(FC.header);
          const EOL = g(FC.eol);
          const csvData = g(FC.csvData, {
            columnsConstraints: {
              minLength: header.length,
              maxLength: header.length,
            },
          });
          const EOF = g(fc.boolean);
          const csv = [
            header.map((v) => escapeField(v, { quote: true })).join(","),
            ...csvData.map((row) =>
              row.map((v) => escapeField(v, { quote: true })).join(","),
            ),
            ...(EOF ? [""] : []),
          ].join(EOL);
          const data =
            csvData.length >= 1
              ? csvData.map((row) =>
                  Object.fromEntries(row.map((v, i) => [header[i], v])),
                )
              : [];
          return { data, csv: new SingleValueReadableStream(csv), header };
        }),
        async ({ data, csv }) => {
          let i = 0;
          for await (const row of parseStringStream(csv)) {
            expect(data[i++]).toEqual(row);
          }
        },
      ),
    ))

Summary: The `parseStringStream` function should correctly parse CSV data from a stream to match an expected data structure derived from the CSV's headers and content.


it("should parse CSV", () =>
    fc.assert(
      fc.asyncProperty(
        fc.gen().map((g) => {
          const header = g(FC.header);
          const EOL = g(FC.eol);
          const csvData = g(FC.csvData, {
            columnsConstraints: {
              minLength: header.length,
              maxLength: header.length,
            },
          });
          const EOF = g(fc.boolean);
          const csv = [
            header.map((v) => escapeField(v, { quote: true })).join(","),
            ...csvData.map((row) =>
              row.map((v) => escapeField(v, { quote: true })).join(","),
            ),
            ...(EOF ? [""] : []),
          ].join(EOL);
          const data =
            csvData.length >= 1
              ? csvData.map((row) =>
                  Object.fromEntries(row.map((v, i) => [header[i], v])),
                )
              : [];
          return { data, csv, header };
        }),
        async ({ data, csv }) => {
          let i = 0;
          for await (const record of parseString(csv)) {
            expect(data[i++]).toEqual(record);
          }
        },
      ),
    ))

Summary: Parsing a CSV string with a specified header and data should produce records that match the expected data structure.


it("should parse CSV", () =>
    fc.assert(
      fc.asyncProperty(
        fc.gen().map((g) => {
          const header = g(FC.header);
          const EOL = g(FC.eol);
          const csvData = g(FC.csvData, {
            columnsConstraints: {
              minLength: header.length,
              maxLength: header.length,
            },
          });
          const EOF = g(fc.boolean);
          const csv = [
            header.map((v) => escapeField(v, { quote: true })).join(","),
            ...csvData.map((row) =>
              row.map((v) => escapeField(v, { quote: true })).join(","),
            ),
            ...(EOF ? [""] : []),
          ].join(EOL);
          const data =
            csvData.length >= 1
              ? csvData.map((row) =>
                  Object.fromEntries(row.map((v, i) => [header[i], v])),
                )
              : [];
          return { data, csv, header };
        }),
        async ({ data, csv }) => {
          let i = 0;
          for (const record of await parseString.toArray(csv)) {
            expect(data[i++]).toEqual(record);
          }
        },
      ),
    ))

Summary: The CSV string generated from headers and data should be accurately parsed into equivalent record objects by `parseString`.


it("should parse CSV", () =>
    fc.assert(
      fc.asyncProperty(
        fc.gen().map((g) => {
          const header = g(FC.header, {
            // TextEncoderStream can't handle utf-16 string.
            fieldConstraints: {
              kindExcludes: ["string16bits"],
            },
          });
          const BOM = g(fc.boolean);
          if (BOM) {
            // Add BOM to the first field.
            header[0] = `\ufeff${header[0]}`;
          }
          const EOL = g(FC.eol);
          const csvData = g(FC.csvData, {
            // TextEncoderStream can't handle utf-16 string.
            fieldConstraints: {
              kindExcludes: ["string16bits"],
            },
            columnsConstraints: {
              minLength: header.length,
              maxLength: header.length,
            },
          });
          const EOF = g(fc.boolean);
          const csv = [
            header.map((v) => escapeField(v, { quote: true })).join(","),
            ...csvData.map((row) =>
              row.map((v) => escapeField(v, { quote: true })).join(","),
            ),
            ...(EOF ? [""] : []),
          ].join(EOL);
          const data =
            csvData.length >= 1
              ? csvData.map((row) =>
                  Object.fromEntries(row.map((v, i) => [header[i], v])),
                )
              : [];
          return {
            data,
            // csv:
            response: new Response(
              new SingleValueReadableStream(csv).pipeThrough(
                new TextEncoderStream(),
              ),
              {
                headers: {
                  "content-type": "text/csv",
                },
              },
            ),
          };
        }),
        async ({ data, response }) => {
          let i = 0;
          for await (const row of parseResponse(response)) {
            expect(data[i++]).toStrictEqual(row);
          }
        },
      ),
    ))

Summary: The parsed CSV from a streamed response should match the expected structured data.


it("should parse CSV", () =>
    fc.assert(
      fc.asyncProperty(
        fc.gen().map((g) => {
          const header = g(FC.header);
          const EOL = g(FC.eol);
          const csvData = g(FC.csvData, {
            columnsConstraints: {
              minLength: header.length,
              maxLength: header.length,
            },
          });
          const EOF = g(fc.boolean);
          const csv = [
            header.map((v) => escapeField(v, { quote: true })).join(","),
            ...csvData.map((row) =>
              row.map((v) => escapeField(v, { quote: true })).join(","),
            ),
            ...(EOF ? [""] : []),
          ].join(EOL);
          const data =
            csvData.length >= 1
              ? csvData.map((row) =>
                  Object.fromEntries(row.map((v, i) => [header[i], v])),
                )
              : [];
          return { data, csv, header };
        }),
        async ({ data, csv }) => {
          let i = 0;
          for (const record of parseString.toArraySync(csv)) {
            expect(data[i++]).toEqual(record);
          }
        },
      ),
    ))

Summary: `parseString.toArraySync` processes a CSV string and produces records matching the expected data structure derived from CSV headers and content.


it("should parse CSV", () =>
    fc.assert(
      fc.asyncProperty(
        fc.gen().map((g) => {
          const header = g(FC.header);
          const EOL = g(FC.eol);
          const csvData = g(FC.csvData, {
            columnsConstraints: {
              minLength: header.length,
              maxLength: header.length,
            },
          });
          const EOF = g(fc.boolean);
          const csv = [
            header.map((v) => escapeField(v, { quote: true })).join(","),
            ...csvData.map((row) =>
              row.map((v) => escapeField(v, { quote: true })).join(","),
            ),
            ...(EOF ? [""] : []),
          ].join(EOL);
          const data =
            csvData.length >= 1
              ? csvData.map((row) =>
                  Object.fromEntries(row.map((v, i) => [header[i], v])),
                )
              : [];
          return { data, csv, header };
        }),
        async ({ data, csv }) => {
          let i = 0;
          for (const record of parseString.toIterableIterator(csv)) {
            expect(data[i++]).toEqual(record);
          }
        },
      ),
    ))

Summary: Parsing a CSV string with headers should yield an iterable sequence of objects whose keys match the headers and values match the corresponding data entries.


it("should parse CSV", () =>
    fc.assert(
      fc.asyncProperty(
        fc.gen().map((g) => {
          const header = g(FC.header);
          const EOL = g(FC.eol);
          const csvData = g(FC.csvData, {
            columnsConstraints: {
              minLength: header.length,
              maxLength: header.length,
            },
          });
          const EOF = g(fc.boolean);
          const csv = [
            header.map((v) => escapeField(v, { quote: true })).join(","),
            ...csvData.map((row) =>
              row.map((v) => escapeField(v, { quote: true })).join(","),
            ),
            ...(EOF ? [""] : []),
          ].join(EOL);
          const data =
            csvData.length >= 1
              ? csvData.map((row) =>
                  Object.fromEntries(row.map((v, i) => [header[i], v])),
                )
              : [];
          return { data, csv, header };
        }),
        async ({ data, csv }) => {
          let i = 0;
          await parseString.toStream(csv).pipeTo(
            new WritableStream({
              write(record) {
                expect(record).toEqual(data[i++]);
              },
            }),
          );
        },
      ),
    ))

Summary: Parsing a CSV string should correctly yield records that match the expected data structure based on provided headers and data content.


it("should parse CSV", () =>
    fc.assert(
      fc.asyncProperty(
        fc.gen().map((g) => {
          const header = g(FC.header, {
            // TextEncoderStream can't handle utf-16 string.
            fieldConstraints: {
              kindExcludes: ["string16bits"],
            },
          });
          const BOM = g(fc.boolean);
          if (BOM) {
            // Add BOM to the first field.
            header[0] = `\ufeff${header[0]}`;
          }

          const EOL = g(FC.eol);
          const csvData = g(FC.csvData, {
            // TextEncoderStream can't handle utf-16 string.
            fieldConstraints: {
              kindExcludes: ["string16bits"],
            },
            columnsConstraints: {
              minLength: header.length,
              maxLength: header.length,
            },
          });
          const EOF = g(fc.boolean);
          const csv = [
            header.map((v) => escapeField(v, { quote: true })).join(","),
            ...csvData.map((row) =>
              row.map((v) => escapeField(v, { quote: true })).join(","),
            ),
            ...(EOF ? [""] : []),
          ].join(EOL);
          const data =
            csvData.length >= 1
              ? csvData.map((row) =>
                  Object.fromEntries(row.map((v, i) => [header[i], v])),
                )
              : [];
          return {
            data,
            csv: new TextEncoder().encode(csv),
          };
        }),
        async ({ data, csv }) => {
          let i = 0;
          for await (const row of parseBinary(csv)) {
            expect(data[i++]).toStrictEqual(row);
          }
        },
      ),
    ))

Summary: Parsing a CSV with variable headers, BOM, EOL, and EOF configurations should correctly convert byte-encoded CSV data into its structured form, matching the expected data objects.


it("should parse ArrayBuffered CSV", () =>
    fc.assert(
      fc.asyncProperty(
        fc.gen().map((g) => {
          const header = g(FC.header, {
            fieldConstraints: {
              kindExcludes: ["string16bits"],
            },
          });
          const EOL = g(FC.eol);
          const EOF = g(fc.boolean);
          const csvData = [
            ...g(FC.csvData, {
              rowsConstraints: {
                minLength: 1,
              },
              columnsConstraints: {
                minLength: header.length,
                maxLength: header.length,
              },
              fieldConstraints: {
                kindExcludes: ["string16bits"],
              },
            }),
            // Last row is not empty for testing.
            g(FC.row, {
              fieldConstraints: {
                minLength: 1,
                kindExcludes: ["string16bits"],
              },
              columnsConstraints: {
                minLength: header.length,
                maxLength: header.length,
              },
            }),
          ];
          const csv = [
            header.map((v) => escapeField(v)).join(","),
            EOL,
            ...csvData.flatMap((row, i) => [
              ...row.map((v) => escapeField(v)).join(","),
              ...(EOF || csvData.length - 1 !== i ? [EOL] : []),
            ]),
          ].join("");
          const data = csvData.map((row) =>
            Object.fromEntries(row.map((v, i) => [header[i], v])),
          );
          return {
            data,
            csv: new TextEncoder().encode(csv).buffer,
          };
        }),
        async ({ data, csv }) => {
          let i = 0;
          for await (const row of parse(csv)) {
            expect(row).toEqual(data[i++]);
          }
        },
      ),
    ))

Summary: `parse` correctly processes an `ArrayBuffer` representing a CSV, ensuring each parsed row matches the expected data.


it("should parse StringReadableStream CSV", () =>
    fc.assert(
      fc.asyncProperty(
        fc.gen().map((g) => {
          const header = g(FC.header);
          const EOL = g(FC.eol);
          const EOF = g(fc.boolean);
          const csvData = [
            ...g(FC.csvData, {
              rowsConstraints: {
                minLength: 1,
              },
              columnsConstraints: {
                minLength: header.length,
                maxLength: header.length,
              },
            }),
            // Last row is not empty for testing.
            g(FC.row, {
              fieldConstraints: {
                minLength: 1,
              },
              columnsConstraints: {
                minLength: header.length,
                maxLength: header.length,
              },
            }),
          ];
          const csv = [
            header.map((v) => escapeField(v)).join(","),
            EOL,
            ...csvData.flatMap((row, i) => [
              ...row.map((v) => escapeField(v)).join(","),
              ...(EOF || csvData.length - 1 !== i ? [EOL] : []),
            ]),
          ].join("");
          const data = csvData.map((row) =>
            Object.fromEntries(row.map((v, i) => [header[i], v])),
          );
          return {
            data,
            csv: new SingleValueReadableStream(csv),
          };
        }),
        async ({ data, csv }) => {
          let i = 0;
          for await (const row of parse(csv)) {
            expect(row).toEqual(data[i++]);
          }
        },
      ),
    ))

Summary: Ensures that parsing a `StringReadableStream` of CSV data correctly reconstructs the original structured data, maintaining the association between headers and values across each row.


it("should parse Uint8ArrayReadableStream CSV", () =>
    fc.assert(
      fc.asyncProperty(
        fc.gen().map((g) => {
          const header = g(FC.header, {
            fieldConstraints: {
              kindExcludes: ["string16bits"],
            },
          });
          const EOL = g(FC.eol);
          const EOF = g(fc.boolean);
          const csvData = [
            ...g(FC.csvData, {
              rowsConstraints: {
                minLength: 1,
              },
              columnsConstraints: {
                minLength: header.length,
                maxLength: header.length,
              },
              fieldConstraints: {
                kindExcludes: ["string16bits"],
              },
            }),
            // Last row is not empty for testing.
            g(FC.row, {
              fieldConstraints: {
                minLength: 1,
                kindExcludes: ["string16bits"],
              },
              columnsConstraints: {
                minLength: header.length,
                maxLength: header.length,
              },
            }),
          ];
          const csv = [
            header.map((v) => escapeField(v)).join(","),
            EOL,
            ...csvData.flatMap((row, i) => [
              ...row.map((v) => escapeField(v)).join(","),
              ...(EOF || csvData.length - 1 !== i ? [EOL] : []),
            ]),
          ].join("");
          const data = csvData.map((row) =>
            Object.fromEntries(row.map((v, i) => [header[i], v])),
          );
          return {
            data,
            csv: new SingleValueReadableStream(new TextEncoder().encode(csv)),
          };
        }),
        async ({ data, csv }) => {
          let i = 0;
          for await (const row of parse(csv)) {
            expect(row).toEqual(data[i++]);
          }
        },
      ),
    ))

Summary: The property ensures that parsing a `Uint8ArrayReadableStream` of CSV data correctly outputs rows matching the expected objects derived from the input data.


it("should parse a CSV with headers by data", () =>
    fc.assert(
      fc.asyncProperty(
        fc.gen().map((g) => {
          const header = g(FC.header);
          const rows = g(FC.csvData, {
            columnsConstraints: {
              minLength: header.length,
              maxLength: header.length,
            },
          });
          const tokens: Token[] = [
            // generate header tokens
            ...header.flatMap<Token>((field, i) => [
              { type: Field, value: field, location: LOCATION_SHAPE },
              i === header.length - 1
                ? {
                    type: RecordDelimiter,
                    value: "\n",
                    location: LOCATION_SHAPE,
                  }
                : {
                    type: FieldDelimiter,
                    value: ",",
                    location: LOCATION_SHAPE,
                  },
            ]),
            // generate rows tokens
            ...rows.flatMap((row) =>
              // generate row tokens
              row.flatMap<Token>((field, j) => [
                { type: Field, value: field, location: LOCATION_SHAPE },
                {
                  type: FieldDelimiter,
                  value: ",",
                  location: LOCATION_SHAPE,
                },
                // generate record delimiter token
                ...((j === row.length - 1
                  ? [
                      {
                        type: RecordDelimiter,
                        value: "\n",
                      },
                    ]
                  : []) as Token[]),
              ]),
            ),
          ];
          const expected = rows.map((row) =>
            Object.fromEntries(row.map((field, i) => [header[i], field])),
          );
          return { tokens, expected };
        }),
        async ({ tokens, expected }) => {
          const actual = await transform(new RecordAssemblerTransformer(), [
            tokens,
          ]);
          expect(actual).toEqual(expected);
        },
      ),
    ))

Summary: Parsing a CSV with headers involves generating tokens for headers and rows, processing these tokens using `RecordAssemblerTransformer`, and ensuring the output matches the expected header-to-data mapping.


it("should separate fields by commas by default", async () => {
    await fc.assert(
      fc.asyncProperty(
        fc.gen().map((g) => {
          const row = g(FC.row);
          const quote = g(FC.quote);
          const chunks = autoChunk(
            g,
            row.map((v) => escapeField(v, { quote })).join(","),
          );
          const expected = [
            ...row.flatMap((value, index) => [
              // If the field is empty or quote is true, add a field.
              ...(quote || value
                ? [{ type: Field, value, location: LOCATION_SHAPE }]
                : []),
              // If the field is not the last field, add a field delimiter.
              ...(index === row.length - 1
                ? []
                : [
                    {
                      type: FieldDelimiter,
                      value: ",",
                      location: LOCATION_SHAPE,
                    },
                  ]),
            ]),
          ];
          return { row, chunks, expected };
        }),
        async ({ chunks, expected }) => {
          const lexer = new LexerTransformer();
          const actual = (await transform(lexer, chunks)).flat();
          expect(actual).toMatchObject(expected);
        },
      ),
    );
  })

Summary: Fields should be separated by commas by default, with expected field types and delimiters matching the transformation result from `LexerTransformer`.


it("should treat the field enclosures as double quotes by default", async () => {
    await fc.assert(
      fc.asyncProperty(
        fc.gen().map((g) => {
          const row = g(FC.row);
          const chunks = autoChunk(
            g,
            row.map((v) => escapeField(v, { quote: true })).join(","),
          );
          const expected = [
            ...row.flatMap((value, index) => [
              { type: Field, value, location: LOCATION_SHAPE },
              ...(index === row.length - 1
                ? []
                : [
                    {
                      type: FieldDelimiter,
                      value: ",",
                      location: LOCATION_SHAPE,
                    },
                  ]),
            ]),
          ];
          return { expected, chunks };
        }),
        async ({ expected, chunks }) => {
          const lexer = new LexerTransformer();
          const actual = (await transform(lexer, chunks)).flat();
          expect(actual).toMatchObject(expected);
        },
      ),
    );
  })

Summary: The test checks that by default, `LexerTransformer` treats field enclosures as double quotes, ensuring that the transformed output matches the expected structure of fields and delimiters.


it("should parse csv with user options", async () => {
    await fc.assert(
      fc.asyncProperty(
        fc.gen().map((g) => {
          const options = g(FC.commonOptions);
          const quote = g(FC.quote);
          const data = g(FC.csvData, {
            fieldConstraints: { minLength: 1 },
            rowsConstraints: { minLength: 1 },
            columnsConstraints: { minLength: 1 },
          });
          const eol = g(FC.eol);
          const EOF = g(fc.boolean);
          const csv =
            data
              .map((row) =>
                row
                  .map((value) => escapeField(value, { quote, ...options }))
                  .join(options.delimiter),
              )
              .join(eol) + (EOF ? eol : "");
          const chunks = autoChunk(g, csv);
          const expected = [
            ...data.flatMap((row, i) => [
              // If row is empty, add a record delimiter.
              ...row.flatMap((value, j) => [
                // If the field is empty or quote is true, add a field.
                ...(quote || value !== "" ? [{ type: Field, value }] : []),
                // If the field is not the last field, add a field delimiter.
                ...(row.length - 1 !== j
                  ? [
                      {
                        type: FieldDelimiter,
                        value: options.delimiter,
                        location: LOCATION_SHAPE,
                      },
                    ]
                  : []),
              ]),
              // If the field is the last field, add a record delimiter.
              ...(data.length - 1 !== i
                ? [
                    {
                      type: RecordDelimiter,
                      value: eol,
                      location: LOCATION_SHAPE,
                    },
                  ]
                : []),
            ]),
          ];
          return { options, chunks, expected };
        }),
        async ({ options, chunks, expected }) => {
          const lexer = new LexerTransformer(options);
          const actual = (await transform(lexer, chunks)).flat();
          expect(actual).toMatchObject(expected);
        },
      ),
      {
        examples: [
          [
            // only EOL is ignored
            {
              options: { delimiter: ",", quotation: '"' },
              chunks: ["\n"],
              expected: [],
            },
          ],
        ],
      },
    );
  })

Summary: Parsing CSV input according to user-defined options results in transformed output that matches the expected structured data representation, considering delimiters, quotations, and end-of-line characters.


it("should lex with comma as a default field delimiter", () => {
    fc.assert(
      fc.property(
        fc.gen().map((g) => {
          const row = g(FC.row);
          const csv = row.map((field) => escapeField(field)).join(",");
          const expected = [
            ...row.flatMap((field, i) => [
              // if field is empty, it should be ignored
              ...(field !== ""
                ? [{ type: Field, value: field, location: LOCATION_SHAPE }]
                : []),
              // if field is not last field, it should be followed by a field delimiter
              ...(row.length - 1 !== i
                ? [
                    {
                      type: FieldDelimiter,
                      value: COMMA,
                      location: LOCATION_SHAPE,
                    },
                  ]
                : []),
            ]),
          ];
          return { csv, expected };
        }),
        ({ csv, expected }) => {
          const lexer = new Lexer();
          const actual = [...lexer.lex(csv)];
          expect(actual).toMatchObject(expected);
        },
      ),
    );
  })

Summary: Lexing a CSV string with comma delimiters should produce expected tokens, ignoring empty fields and adding delimiters appropriately.


it("should lex with double quote as a default field quotation", () => {
    fc.assert(
      fc.property(
        fc.gen().map((g) => {
          const row = g(FC.row);
          const csv = row
            // field should be escaped with double quote
            .map((field) => escapeField(field, { quote: true, quotation: '"' }))
            .join(",");
          const expected = [
            ...row.flatMap((field, i) => [
              // field should be escaped with double quote,
              // so empty field should be escaped with double quote
              { type: Field, value: field, location: LOCATION_SHAPE },
              // if field is not last field, it should be followed by a field delimiter
              ...(row.length - 1 !== i
                ? [
                    {
                      type: FieldDelimiter,
                      value: COMMA,
                      location: LOCATION_SHAPE,
                    },
                  ]
                : []),
            ]),
          ];
          return { csv, expected };
        }),
        ({ csv, expected }) => {
          const lexer = new Lexer();
          const actual = [...lexer.lex(csv)];
          expect(actual).toMatchObject(expected);
        },
      ),
    );
  })

Summary: Lexing a CSV string with fields quoted by double quotes should match an array of expected field and delimiter tokens.


it("should lex with with user given delimiter", () => {
    fc.assert(
      fc.property(
        fc.gen().map((g) => {
          const { delimiter } = g(FC.commonOptions, {
            quotation: DOUBLE_QUOTE,
          });

          const row = g(FC.row);
          const csv = row
            .map((field) => escapeField(field, { delimiter }))
            .join(delimiter);
          const expected = [
            ...row.flatMap((field, i) => [
              // if field is empty, it should be ignored
              ...(field !== "" || escapeField(field, { delimiter }) !== field
                ? [{ type: Field, value: field, location: LOCATION_SHAPE }]
                : []),
              // if field is not last field, it should be followed by a field delimiter
              ...(row.length - 1 !== i
                ? [
                    {
                      type: FieldDelimiter,
                      value: delimiter,
                      location: LOCATION_SHAPE,
                    },
                  ]
                : []),
            ]),
          ];
          return { delimiter, csv, expected };
        }),
        ({ delimiter, csv, expected }) => {
          const lexer = new Lexer({ delimiter });
          const actual = [...lexer.lex(csv)];
          expect(actual).toMatchObject(expected);
        },
      ),
    );
  })

Summary: Lexing with a user-defined delimiter should produce tokens, ignoring empty fields unless they are escaped, and include field delimiters for fields that are not the last in a row.


it("should lex with with user given quotation", () => {
    fc.assert(
      fc.property(
        fc.gen().map((g) => {
          const { quotation } = g(FC.commonOptions, { delimiter: COMMA });
          const row = g(FC.row);
          const csv = row
            .map((field) => escapeField(field, { quotation }))
            .join(",");
          const expected = [
            ...row.flatMap((field, i) => [
              // if field is empty, it should be ignored
              ...(field !== ""
                ? [{ type: Field, value: field, location: LOCATION_SHAPE }]
                : []),
              // if field is not last field, it should be followed by a field delimiter
              ...(row.length - 1 !== i
                ? [
                    {
                      type: FieldDelimiter,
                      value: COMMA,
                      location: LOCATION_SHAPE,
                    },
                  ]
                : []),
            ]),
          ];
          return { quotation, csv, expected };
        }),
        ({ quotation, csv, expected }) => {
          const lexer = new Lexer({ quotation });
          const actual = [...lexer.lex(csv)];
          expect(actual).toMatchObject(expected);
        },
      ),
    );
  })

Summary: The lexer processes CSV input with a specified quotation character and produces tokenized output that matches the expected field and delimiter structure, correctly handling empty fields.


it("should detect record delimiter", () => {
    fc.assert(
      fc.property(
        fc.gen().map((g) => {
          const options = g(FC.commonOptions);
          const eol = g(FC.eol);
          const data = g(FC.csvData, {
            fieldConstraints: { minLength: 1 },
            rowsConstraints: { minLength: 1 },
            columnsConstraints: { minLength: 1 },
          });
          const EOF = g(fc.boolean);
          const quote = g(FC.quote);
          const csv =
            data
              .map((row) =>
                row
                  .map((field) => escapeField(field, { ...options, quote }))
                  .join(options.delimiter),
              )
              .join(eol) + (EOF ? eol : "");
          const expected = [
            ...data.flatMap((row, i) => [
              ...row.flatMap((field, j) => [
                // if quote is false and field is empty, it should be ignored
                ...(quote || field !== ""
                  ? [{ type: Field, value: field }]
                  : []),
                // if field is not last field, it should be followed by a field delimiter
                ...(row.length - 1 !== j
                  ? [
                      {
                        type: FieldDelimiter,
                        value: options.delimiter,
                        location: LOCATION_SHAPE,
                      },
                    ]
                  : []),
              ]),
              // if row is not last row, it should be followed by a record delimiter.
              ...(data.length - 1 !== i
                ? [
                    {
                      type: RecordDelimiter,
                      value: eol,
                      location: LOCATION_SHAPE,
                    },
                  ]
                : []),
            ]),
          ];
          return { csv, data, options, expected };
        }),
        ({ options, csv, expected }) => {
          const lexer = new Lexer(options);
          const actual = [...lexer.lex(csv)];
          expect(actual).toMatchObject(expected);
        },
      ),
    );
  })

Summary: Lexer should correctly identify record delimiters in CSV data.


it("it should be a same result when given one CSV at a time and when given chunked CSVs", () => {
    fc.assert(
      fc.property(
        fc.gen().map((g) => {
          const options = g(FC.commonOptions);
          const eol = g(FC.eol);
          const data = g(FC.csvData);
          const EOF = g(fc.boolean);
          const quote = g(FC.quote);
          const csv =
            data
              .map((row) =>
                row
                  .map((field) => escapeField(field, { ...options, quote }))
                  .join(options.delimiter),
              )
              .join(eol) + (EOF ? eol : "");
          const chunks = autoChunk(g, csv);
          return {
            csv,
            chunks,
            options,
          };
        }),
        ({ options, csv, chunks }) => {
          // lexer1 is used to compare with lexer2
          const lexer1 = new Lexer(options);
          const expected = [...lexer1.lex(csv)];

          // lexer2 is used to lex chunked data
          const lexer2 = new Lexer(options);
          const actual = [
            // lex chunked data
            ...chunks.flatMap((chunk) => [...lexer2.lex(chunk, true)]),
            // flush lexer2
            ...lexer2.flush(),
          ];
          expect(actual).toMatchObject(expected);
        },
      ),
    );
  })

Summary: The lexer should produce the same result whether processing an entire CSV at once or processing it in chunks.


it("should parse a CSV with headers by data", () => {
    fc.assert(
      fc.asyncProperty(
        fc.gen().map((g) => {
          const EOL = g(FC.eol);
          const header = g(FC.header);
          const rows = g(FC.csvData, {
            columnsConstraints: {
              minLength: header.length,
              maxLength: header.length,
            },
          });
          const tokens = [
            // generate header tokens
            ...header.flatMap<Token>((field, i) => [
              { type: Field, value: field, location: LOCATION_SHAPE },
              i === header.length - 1
                ? {
                    type: RecordDelimiter,
                    value: EOL,
                    location: LOCATION_SHAPE,
                  }
                : {
                    type: FieldDelimiter,
                    value: ",",
                    location: LOCATION_SHAPE,
                  },
            ]),
            // generate rows tokens
            ...rows.flatMap<Token>((row) =>
              // generate row tokens
              row.flatMap<Token>((field, j) => [
                { type: Field, value: field, location: LOCATION_SHAPE },
                { type: FieldDelimiter, value: ",", location: LOCATION_SHAPE },
                // generate record delimiter token
                ...((j === row.length - 1
                  ? [
                      {
                        type: RecordDelimiter,
                        value: LF,
                      },
                    ]
                  : []) as Token[]),
              ]),
            ),
          ];
          const expected = rows.map((row) =>
            Object.fromEntries(row.map((field, i) => [header[i], field])),
          );
          return { tokens, expected };
        }),
        async ({ tokens, expected }) => {
          const assembler = new RecordAssembler();
          const actual = [...assembler.assemble(tokens, true)];
          expect(actual).toEqual(expected);
        },
      ),
    );
  })

Summary: Parsing a CSV with headers should produce data objects matching the expected structure based on the tokens generated from headers and data rows.


test('Start of week is in the past', () => {
  fc.assert(
    fc.property(
      fcCalendarDate().filter(({ year }) => 1900 < year && year < 2100),
      (date) => {
        const startOfThisWeek = startOfWeek(date);
        expect(areInOrder(startOfThisWeek, date)).toBe(true);
        expect(
          numberOfDaysBetween({ start: startOfThisWeek, end: date }),
        ).toBeLessThan(7);
        expect(dayOfWeek(startOfThisWeek)).toBe('mon');
      },
    ),
  );
})

Summary: The start of the week for a given date should be prior to or the same as the date, within 7 days before it, and always be a Monday.


it("should parse a CSV with headers by option", () => {
    fc.assert(
      fc.asyncProperty(
        fc.gen().map((g) => {
          const header = g(FC.header);
          const rows = g(FC.csvData, {
            columnsConstraints: {
              minLength: header.length,
              maxLength: header.length,
            },
          });
          const tokens: Token[] = [
            ...rows.flatMap<Token>((row) =>
              row.flatMap<Token>((field, j) => [
                { type: Field, value: field, location: LOCATION_SHAPE },
                { type: FieldDelimiter, value: ",", location: LOCATION_SHAPE },
                ...((j === row.length - 1
                  ? [
                      {
                        type: RecordDelimiter,
                        value: LF,
                      },
                    ]
                  : []) as Token[]),
              ]),
            ),
          ];
          const expected = rows.map((row) =>
            Object.fromEntries(row.map((field, i) => [header[i], field])),
          );
          return { header, tokens, expected };
        }),
        async ({ header, tokens, expected }) => {
          const assembler = new RecordAssembler({ header });
          const actual = [...assembler.assemble(tokens, true)];
          expect(actual).toEqual(expected);
        },
      ),
    );
  })

Summary: The test verifies that `RecordAssembler` correctly parses CSV data with headers into structured records when tokens are provided, ensuring the output matches the expected mapping of header to field.


test('Number of days in month', () => {
  fc.assert(
    fc.property(fcCalendarDate(), (date) => {
      const startOfMonth: CalendarDate = { ...date, day: 1 };
      const endOfMonth: CalendarDate = {
        ...date,
        day: numberOfDaysInMonth(date),
      };
      const allDaysInMonth = periodOfDates(startOfMonth, endOfMonth);
      const numberOfDays =
        numberOfDaysBetween({
          start: startOfMonth,
          end: endOfMonth,
        }) + 1;
      expect(allDaysInMonth.length).toBe(numberOfDays);
      expect(numberOfDays).toBe(numberOfDaysInMonth(date));
      expect([28, 29, 30, 31]).toContain(numberOfDays);
    }),
  );
})

Summary: The test checks that the number of days calculated in a month corresponds to the days generated by `periodOfDates` and matches the expected values of 28, 29, 30, or 31.


test('Are-in-order has positive diff', () => {
  fc.assert(
    fc.property(fcCalendarDate(), fcCalendarDate(), (a, b) => {
      const areOrdered = areInOrder(a, b);
      const diff = numberOfDaysBetween({ start: a, end: b });
      const negativeDiff = diff < 0;
      expect(!areOrdered).toBe(negativeDiff);
    }),
  );
})

Summary: Verifies that if two calendar dates are not in order, the difference in days between them is negative.


test('Normalization works', () => {
  fc.assert(
    fc.property(
      fcCalendarDate(),
      fc.integer(-100 * 365, 100 * 365),
      (date, n) => {
        const otherDate = addDays(date, n);
        const sameOtherDate = addDays({ ...date, day: date.day + n }, 0);
        expect(otherDate).toEqual(sameOtherDate);
      },
    ),
  );
})

Summary: Adding `n` days to a calendar date should yield the same result whether using the `addDays` function directly or by altering the date's day field before applying `addDays` with zero additional days.


test('Ordered dates', () => {
  fc.assert(
    fc.property(fc.array(fcCalendarDate()), (dates) => {
      const orderedDates = dates.sort((a, b) =>
        isDateBefore(a, b) ? -1 : datesEqual(a, b) ? 0 : 1,
      );
      expect(areInOrder(...orderedDates)).toBe(true);
    }),
  );
})

Summary: Dates sorted by `isDateBefore` and `datesEqual` should be in order as verified by `areInOrder`.


test('Start of week is in the past for any start of week', () => {
  fc.assert(
    fc.property(
      fcCalendarDate().filter(({ year }) => 1900 < year && year < 2100),
      fcWeekDay(),
      (date, weekStart) => {
        const startOfThisWeek = startOfWeek(date, {
          firstDayOfWeek: weekStart,
        });
        expect(areInOrder(startOfThisWeek, date)).toBe(true);
        expect(
          numberOfDaysBetween({ start: startOfThisWeek, end: date }),
        ).toBeLessThan(7);
        expect(dayOfWeek(startOfThisWeek)).toBe(weekStart);
      },
    ),
  );
})

Summary: The start of the week for any given date and weekday must precede or coincide with the date, be less than 7 days apart, and match the specified weekday.


test('Adding zero months', () => {
  fc.assert(
    fc.property(fcCalendarDate(), (date) => {
      const lastMonth = addMonths(date, 0);
      const lastDate = lastDateInMonth(lastMonth);
      const diffInDays = numberOfDaysBetween({ start: date, end: lastDate });

      if (datesEqual(date, lastDateInMonth(date))) {
        expect(diffInDays).toBe(0);
      } else {
        expect(diffInDays).toBeGreaterThan(0);
      }

      expect(diffInDays).toBeLessThan(31);
    }),
  );
})

Summary: Adding zero months to a date should result in a difference of fewer than 31 days, with a difference of 0 if the date is the last day of the month.


test('Serialize and parse', () => {
  fc.assert(
    fc.property(fcCalendarDate(), (date) => {
      const serialized = serializeIso8601String(date);
      const parsed = parseIso8601String(serialized);
      expect(parsed).toEqual(date);
    }),
  );
})

Summary: Serialization of a date to an ISO 8601 string and its subsequent parsing result in the original date.


test('Parse', () => {
  fc.assert(
    fc.property(
      fcYear(),
      fc.integer(0, 9),
      fc.integer(0, 9),
      fc.integer(0, 9),
      fc.integer(0, 9),
      (yearNumber, monthDigit_1, monthDigit_2, dayDigit_1, dayDigit_2) => {
        const mm = `${monthDigit_1}${monthDigit_2}`;
        const dd = `${dayDigit_1}${dayDigit_2}`;

        const yyyymmdd = `${yearNumber}-${mm}-${dd}`;
        const parse = () => parseIso8601String(yyyymmdd);

        if (yearNumber < 1000 || yearNumber > 9999) {
          // Won't be zero-padded
          expect(parse).toThrow();
          return;
        }

        const month = parseInt(mm, 10);
        const day = parseInt(dd, 10);

        if (month === 0 || day === 0 || month > 12 || day > 31) {
          expect(parse).toThrow();
          return;
        }

        if (day <= 28) {
          const calendarDate = {
            year: yearNumber,
            month: monthName(month),
            day,
          };
          expect(parseIso8601String(yyyymmdd)).toEqual(calendarDate);
          expect(parseIso8601String(yyyymmdd + 'T00:00Z')).toEqual(
            calendarDate,
          );
        }

        if (month < 10 || day < 10) {
          // One of these won't be zero padded, so we expect a failure
          const yyyymd = `${yearNumber}-${month}-${day}`;
          expect(() => parseIso8601String(yyyymd)).toThrow();
        }

        // Here we may or may not be legit, depending on the number of days in this month
        // so we test some gibberis for good measure
        expect(() => parseIso8601String('1' + yyyymmdd)).toThrow();
        expect(() =>
          parseIso8601String(yyyymmdd.split('').reverse().join()),
        ).toThrow();
        expect(() => parseIso8601String(yyyymmdd.replace('-', '/'))).toThrow();
      },
    ),
  );
})

Summary: Verifies `parseIso8601String` correctly parses strings in YYYY-MM-DD format for valid dates, throws for invalid month/day values, non-zero-padded numbers, malformed strings, or unsupported formats.


test('Adding months', () => {
  fc.assert(
    fc.property(
      fcCalendarDate(),
      fc.integer(-50 * 12, 50 * 12),
      (date, months) => {
        const lastMonth = addMonths(date, months);
        const lastDate = lastDateInMonth(lastMonth);
        const diffInDays = numberOfDaysBetween({ start: date, end: lastDate });

        if (months !== 0) {
          expect(Math.sign(diffInDays)).toBe(Math.sign(months));
        }
      },
    ),
  );
})

Summary: Adding or subtracting months from a date should result in a difference in days that matches the sign of the number of months added or subtracted.


test('Convert from js Date object', () => {
  fc.assert(
    fc.property(fcCalendarDate(), (date) => {
      const { year, month, day } = date;
      const monthIndex = monthNumber(month) - 1;

      const jsDate = new Date(year, monthIndex, day);
      const newDate = calendarDateFromJsDateObject(jsDate);

      expect(date).toEqual(newDate);
    }),
  );
})

Summary: Converting a JavaScript `Date` object back into a calendar date should yield an equivalent `CalendarDate` object.


test('Values correctly converted', () => {
  fc.assert(
    fc.property(propsDescArbitrary, (propsDesc) => {
      const SourceComponent = jest.fn()
      const propsDescPairs = Object.entries(propsDesc)
      const props = Object.fromEntries(propsDescPairs.map(([key, [source]]) => [key, source]))
      const converters = new Map(propsDescPairs.map(([key, [, target]]) => [key, jest.fn().mockReturnValue(target)]))
      const ProxiedComponent = makePropsValueProxy(SourceComponent, converters)

      ProxiedComponent(props)

      expect(SourceComponent.mock.calls.length).toBe(1)
      expect(SourceComponent.mock.calls[0]).toEqual([
        Object.fromEntries(propsDescPairs.map(([key, [, target]]) => [key, target])),
      ])
      ;[...converters.entries()].forEach(([key, converter]) => {
        expect(converter.mock.calls.length).toBe(1)
        expect(converter.mock.calls[0]).toEqual([props[key]])
      })
    })
  )
})

Summary: `makePropsValueProxy` ensures that each property in `props` is correctly converted using provided converters, and the converted values are passed to the `SourceComponent`.


test('Keys correctly mapped', () => {
  fc.assert(
    fc.property(propsDescArbitrary, (propsDesc) => {
      const SourceComponent = jest.fn()
      const propsDescPairs = Object.entries(propsDesc)
      const keysMap = new Map(propsDescPairs.map(([sourceKey, [targetKey]]) => [sourceKey, targetKey]))
      const props = Object.fromEntries(propsDescPairs.map(([, [targetKey, targetValue]]) => [targetKey, targetValue]))
      const ProxiedComponent = makePropsKeyProxy(SourceComponent, keysMap)

      ProxiedComponent(props)

      expect(SourceComponent.mock.calls.length).toBe(1)
      expect(SourceComponent.mock.calls[0]).toEqual([
        Object.fromEntries(propsDescPairs.map(([sourceKey, [, targetValue]]) => [sourceKey, targetValue])),
      ])
    })
  )
})

Summary: Verifies that `makePropsKeyProxy` correctly maps and calls `SourceComponent` with properties keyed by the original source keys using the provided keys map.


test('Return component from rootHook', () => {
  fc.assert(
    fc.property(
      fc.dictionary(fc.string(), fc.anything()),
      fc.dictionary(fc.string(), fc.anything()),
      fc.nat({ max: 5 }),
      (value, componentProps, callCount) => {
        const componentSpy = jest.fn()
        const rootHookSpy = jest.fn().mockReturnValue([componentSpy, componentProps])

        const { result } = renderHook(() => useRecord(rootHookSpy, value, jest.fn()))
        const [component, props] = result.current

        for (let i = 0; i < callCount; i++) {
          component(props)
        }

        expect(componentSpy).toBeCalledTimes(callCount)
        expect(componentSpy.mock.calls).toEqual(Array(callCount).fill([componentProps]))
      }
    )
  )
})

Summary: `useRecord` hook, when invoked with a `rootHook` that returns a component and props, should return a component that is called the specified number of times with the correct props.


test('By default all fields untouched, has not errors and not in validating', () => {
  fc.assert(
    fc.property(fc.dictionary(fc.string(), fc.anything()), (value) => {
      const rootHookSpy = jest.fn()

      renderHook(() => useRecord(rootHookSpy, value, jest.fn()))

      expect((rootHookSpy.mock.calls[0] as unknown[])[0]).toEqual(
        Object.fromEntries(
          Object.entries(value).map(([key, item]) => [
            key,
            {
              value: item,
              isTouched: false,
              relatedRef: null,
            },
          ])
        )
      )
    })
  )
})

Summary: All fields should be initially untouched, with no errors and not in the validation state when using `useRecord`.


test('Passthroughs to recordHook same record for same value on rerender', () => {
  fc.assert(
    fc.property(fc.dictionary(fc.string(), fc.anything()), (value) => {
      const rootHookSpy = jest.fn()

      const { rerender } = renderHook(() => useRecord(rootHookSpy, value, jest.fn()))
      rerender()

      expect((rootHookSpy.mock.calls[0] as unknown[])[0]).toBe((rootHookSpy.mock.calls[1] as unknown[])[0])
    })
  )
})

Summary: `useRecord` ensures the same record is passed to `rootHookSpy` upon re-rendering with the same value.


test('Return component from rootHook', () => {
  fc.assert(
    fc.property(
      fc.array(fc.anything()),
      fc.dictionary(fc.string(), fc.anything()),
      fc.nat({ max: 5 }),
      (value, componentProps, callCount) => {
        const componentSpy = jest.fn()
        const rootHookSpy = jest.fn().mockReturnValue([componentSpy, componentProps])

        const { result } = renderHook(() => useArray(rootHookSpy, value, jest.fn(), getKey))
        const [component, props] = result.current

        for (let i = 0; i < callCount; i++) {
          component(props)
        }

        expect(componentSpy).toBeCalledTimes(callCount)
        expect(componentSpy.mock.calls).toEqual(Array(callCount).fill([componentProps]))
      }
    )
  )
})

Summary: The property ensures that `useArray` returns a component and props from `rootHook`, and that the component, when invoked multiple times, calls the expected spy with the correct props for the specified number of calls.


test('By default all fields untouched, has not errors and not in validating', () => {
  fc.assert(
    fc.property(fc.array(fc.anything()), (value) => {
      const rootHookSpy = jest.fn()

      renderHook(() => useArray(rootHookSpy, value, jest.fn(), getKey))

      expect((rootHookSpy.mock.calls[0] as unknown[])[0]).toEqual(
        value.map((item) => ({
          value: item,
          isTouched: false,
          relatedRef: null,
        }))
      )
    })
  )
})

Summary: All fields in the `useArray` hook are initially untouched, have no errors, and are not in a validating state.


test('Passthroughs to recordHook same record for same value on rerender', () => {
  fc.assert(
    fc.property(fc.array(fc.anything()), (value) => {
      const rootHookSpy = jest.fn()

      const { rerender } = renderHook(() => useArray(rootHookSpy, value, jest.fn(), getKey))
      rerender()

      expect((rootHookSpy.mock.calls[0] as unknown[])[0]).toBe((rootHookSpy.mock.calls[1] as unknown[])[0])
    })
  )
})

Summary: The test verifies that the `useArray` hook triggers the `rootHookSpy` with the same record for the same value on rerender.


test('Getter from context return correct value', () => {
  fc.assert(
    fc.property(recordArbitrary, (record) => {
      const { result } = renderHook(() => useRecordRoot(TestContext, record, jest.fn()))
      const getter = result.current[1].value[0]

      const gotValue = Object.fromEntries(Object.keys(record).map((key) => [key, getter(key)]))

      expect(gotValue).toEqual(record)
    })
  )
})

Summary: The getter function from the rendered hook should return values that reconstruct the original record when mapped over its keys.


test('Getter from context throw error if key not exists in data', () => {
  fc.assert(
    fc.property(recordWithInvalidKeyArbitrary, ([record, invalidKey]) => {
      const { result } = renderHook(() => useRecordRoot(TestContext, record, jest.fn()))
      const getter = result.current[1].value[0]

      expect(() => getter(invalidKey)).toThrow(RangeError)
    })
  )
})

Summary: An error is thrown when attempting to retrieve a non-existent key using a getter from the context.


test('Updater from context update correct value', () => {
  fc.assert(
    fc.property(recordWithNewValueArbitrary, ([record, value]) => {
      const onChangeSpy = jest.fn().mockImplementation((cb: (prev: Record<string, unknown>) => void) => cb(record))
      const { result } = renderHook(() => useRecordRoot(TestContext, record, onChangeSpy))
      const updater = result.current[1].value[1]

      Object.keys(record).forEach((key) => {
        const keyUpdaterSpy = jest.fn<unknown, unknown[]>().mockReturnValue(value)
        updater(key, keyUpdaterSpy)

        expect(keyUpdaterSpy.mock.calls[0][0]).toBe(record[key])
        expect(onChangeSpy).toHaveLastReturnedWith({ ...record, [key]: value })
      })
    })
  )
})

Summary: Updating context using `useRecordRoot` changes the specified key's value in the record and triggers the `onChange` callback with the updated record.


test('Updater from context throw error if key not exists in data', () => {
  fc.assert(
    fc.property(recordWithInvalidKeyArbitrary, ([record, invalidKey]) => {
      const onChangeSpy = jest.fn().mockImplementation((cb: (prev: Record<string, unknown>) => void) => cb(record))
      const { result } = renderHook(() => useRecordRoot(TestContext, record, onChangeSpy))
      const updater = result.current[1].value[1]

      expect(() => updater(invalidKey, () => undefined)).toThrow(RangeError)
    })
  )
})

Summary: Using an updater with a non-existing key in the context data should throw a `RangeError`.


test('Updater try to set value from context by key', () => {
  fc.assert(
    fc.property(keyArbitrary, (key) => {
      const setterSpy = jest.fn()
      const { result } = renderHook(() => useLeaf(TestContext, key), {
        wrapper: ({ children }) => createElement(TestContext.Provider, { value: [jest.fn(), setterSpy] }, children),
      })

      const updateLeaf = result.current[1]
      const updater = (): void => undefined
      updateLeaf(updater)

      expect(setterSpy.mock.calls[0]).toEqual([key, updater])
    })
  )
})

Summary: `useLeaf` hook correctly calls the context setter function with a key and an updater function.


test('Getter from context return correct value', () => {
  fc.assert(
    fc.property(arrayArbitrary, (array) => {
      const { result } = renderHook(() => useArrayRoot(TestContext, array, jest.fn(), getKeyIndex))
      const getter = result.current[1].value[0]

      const gotValue = array.map((_, index) => getter(index))

      expect(gotValue).toEqual(array)
    })
  )
})

Summary: The getter function from `useArrayRoot` correctly retrieves values from an array based on index.


test('Getter from context throw error if key not exists in data', () => {
  fc.assert(
    fc.property(arrayWithInvalidKeyArbitrary, ([array, invalidKey]) => {
      const { result } = renderHook(() => useArrayRoot(TestContext, array, jest.fn(), getKeyIndex))
      const getter = result.current[1].value[0]

      expect(() => getter(invalidKey)).toThrow(RangeError)
    })
  )
})

Summary: Calling the getter from the context should throw a `RangeError` when a non-existent key is accessed in the data.


test('Updater from context update correct value', () => {
  fc.assert(
    fc.property(arrayWithNewValueArbitrary, ([array, value]) => {
      const onChangeSpy = jest.fn().mockImplementation((cb: (prev: unknown[]) => void) => cb(array))
      const { result } = renderHook(() => useArrayRoot(TestContext, array, onChangeSpy, getKeyIndex))
      const updater = result.current[1].value[1]

      array.forEach((_, index) => {
        const keyUpdaterSpy = jest.fn<unknown, unknown[]>().mockReturnValue(value)
        updater(index, keyUpdaterSpy)

        expect(keyUpdaterSpy.mock.calls[0][0]).toBe(array[index])
        const updated = [...array]
        updated[index] = value
        expect(onChangeSpy).toHaveLastReturnedWith(updated)
      })
    })
  )
})

Summary: Updating a context with a new array value should result in the updated array being correctly modified and returned.


test('onChange callback has called on set new value', () => {
  fc.assert(
    fc.property(arrayWithNewValueArbitrary, ([array, value]) => {
      const onChangeSpy = jest.fn()
      const { result } = renderHook(() => useArrayRoot(TestContext, array, onChangeSpy, getKeyIndex))
      const updater = result.current[1].value[1]

      array.forEach((_, key) => {
        updater(key, jest.fn().mockReturnValue(value))
      })

      expect(onChangeSpy.mock.calls.length).toBe(Object.keys(array).length)
    })
  )
})

Summary: Updating an array element using `updater` should trigger the `onChange` callback for each element, resulting in `onChangeSpy` being called as many times as there are elements in the array.


test('Return items same length as fields prop', () => {
  fc.assert(
    fc.property(fc.array(fieldArbitrary), (fields) => {
      const result = fieldsBuilder(jest.fn(), { fields })

      expect(result.length).toBe(fields.length)
    })
  )
})

Summary: The output from `fieldsBuilder` should have the same length as the input `fields` array.


test('Get exists string path from object return correct value', () => {
  fc.assert(
    fc.property(existsTupleArbitrary, ([obj, path, val]) => {
      expect(get(obj, path.join('.'))).toBe(val)
    })
  )
})

Summary: `get` retrieves the correct value from an object when provided with an existing string path.


test('Get exists string array path from object return correct value', () => {
  fc.assert(
    fc.property(existsTupleArbitrary, ([obj, path, val]) => {
      expect(get(obj, path)).toBe(val)
    })
  )
})

Summary: `get` retrieves the correct value from an object using an existing string array path.


test('Get not exists string path from object return undefined', () => {
  fc.assert(
    fc.property(notExistsTupleArbitrary, ([obj, path]) => {
      expect(get(obj, path.join('.'))).toBeUndefined()
    })
  )
})

Summary: Getting a non-existent string path from an object should return undefined.


test('Get not exists string array path from object return undefined', () => {
  fc.assert(
    fc.property(notExistsTupleArbitrary, ([obj, path]) => {
      expect(get(obj, path)).toBeUndefined()
    })
  )
})

Summary: `get` returns `undefined` when accessing a non-existent array path in an object.


it("Should sign and verify random messages", () => {
    fc.assert(
      fc.property(
        fc.record({
          msg: fc.uint8Array({ size: "large" }),
          sk: fc.uint8Array({ minLength: 32, maxLength: 32 })
        }),
        ({ msg, sk }) => {
          const pk = getPublicKey(hex.encode(sk));
          const signature = sign(msg, sk);

          expect(verify(msg, signature, pk)).to.be.true;
          expect(verify_signature(Address.from_public_key(pk), msg, signature)).to.be.true;
        }
      )
    );
  })

Summary: Signing and verifying random messages with generated keys should be successful, ensuring both verification methods return true.


test("SByte fuzzing", () => {
    fc.assert(
      fc.property(fc.integer({ min: MIN_I8, max: MAX_I8 }), (val) => {
        const serialized = SByte(val).toHex();
        expect(serialized).to.be.equal(Value$.ofByte(val).toHex()); // ensure compatibility with sigmastate-js
        expect(SConstant.from(serialized).data).to.be.equal(val);
      })
    );
  })

Summary: The test verifies that serializing an integer as an `SByte` to hexadecimal is consistent with `Value$.ofByte`, and that deserialization through `SConstant` retrieves the original integer.


test("SInt fuzzing", () => {
    fc.assert(
      fc.property(fc.integer({ min: MIN_I32, max: MAX_I32 }), (val) => {
        const serialized = SInt(val).toHex();
        expect(serialized).to.be.equal(Value$.ofInt(val).toHex()); // ensure compatibility with sigmastate-js
        expect(SConstant.from(serialized).data).to.be.equal(val);
      })
    );
  })

Summary: Serializing and deserializing an integer within a defined range should yield consistent hexadecimal outputs and data values, ensuring compatibility with `sigmastate-js`.


test("SLong fuzzing", () => {
    fc.assert(
      fc.property(fc.bigInt({ min: MIN_I64, max: MAX_I64 }), (val) => {
        const serialized = SLong(val).toHex();
        expect(serialized).to.be.equal(Value$.ofLong(val).toHex()); // ensure compatibility with sigmastate-js
        expect(SConstant.from(serialized).data).to.be.equal(val);
      })
    );
  })

Summary: `SLong` serialization of a `bigInt` within the range `MIN_I64` to `MAX_I64` should produce a hex string equivalent to `Value$.ofLong`, and `SConstant` should deserialize it back to the original value.


test("SBigInt fuzzing", () => {
    fc.assert(
      fc.property(fc.bigInt({ min: MIN_I256, max: MAX_I256 }), (val) => {
        const serialized = SBigInt(val).toHex();
        expect(serialized).to.be.equal(Value$.ofBigInt(val).toHex()); // ensure compatibility with sigmastate-js
        expect(SConstant.from(serialized).data).to.be.equal(val);
      })
    );
  })

Summary: SBigInt serialization should produce a hexadecimal value that matches `Value$.ofBigInt` and deserializes back to the original bigint.


test("Round-trip fuzzing", () => {
    fc.assert(
      fc.property(fc.bigInt({ min: MIN_I64, max: MAX_I64 }), (n) => {
        expect(zigZag64.decode(zigZag64.encode(n))).to.be.equal(n);
      })
    );
  })

Summary: Encoding and then decoding an integer using `zigZag64` should return the original integer within the specified range.


bench("should add properties successfully", async () => {
    expect(
      await overrideSvgAttributes("<SVG></SVG>", {
        height: null,
        width: null,
        "aria-hidden": true,
        "aria-label": null,
        viewBox: "0 0 2712 894",
      }),
    ).toBe('<SVG aria-hidden="true" viewBox="0 0 2712 894"></SVG>');

    await fc.assert(
      fc.asyncProperty(
        svgArbitrary,
        SVGAttributesArbitrary,
        fc.context(),
        async (svgSource, overrides, ctx) => {
          ctx.log(svgSource);
          const transformedSource = await overrideSvgAttributes(
            svgSource,
            overrides,
          );
          ctx.log(transformedSource);
          expect(transformedSource).toBeTruthy();
          // every truthy override should exist in the transformed source
          Object.entries(overrides)
            .filter(([, value]) => {
              ctx.log(`${value}, ${!!value}`);
              return !!value;
            })
            .forEach(([override]) => {
              expect(transformedSource).toContain(override);
            });
        },
      ),
    );
  })

Summary: `overrideSvgAttributes` correctly adds only truthy attribute overrides to an SVG string.


test("byte -> hex -> byte", () => {
    fc.assert(
      fc.property(fc.uint8Array({ size: "medium" }), (bytes) => {
        expect(hex.decode(hex.encode(bytes))).to.be.deep.equal(bytes);
      })
    );
  })

Summary: Encoding a byte array to hex and decoding it back should return the original byte array.


test("hex -> byte -> hex", () => {
    fc.assert(
      fc.property(paddedHexArb, (hexString) => {
        expect(hex.encode(hex.decode(hexString))).to.be.equal(hexString.toLowerCase());
      })
    );
  })

Summary: Encoding and then decoding a hex string should return the original string in lowercase.


it("Should encode/decode radom numbers", () => {
    fc.assert(
      fc.property(fc.integer({ min: 0, max: 100000000 }), (n) => {
        expect(readVLQ(new SigmaByteReader(toVLQBytes(n)))).to.be.equal(n);
      })
    );
  })

Summary: Encoding and decoding a random integer using VLQ should return the original integer.


it("Should encode/decode radom numbers", () => {
    fc.assert(
      fc.property(fc.bigInt({ min: 0n, max: BigInt(Number.MAX_SAFE_INTEGER) }), (n) => {
        expect(readBigVLQ(new SigmaByteReader(toBigVLQBytes(n)))).to.be.equal(n);
      })
    );
  })

Summary: Encoding and decoding random numbers should result in values matching the original input using `readBigVLQ` and `toBigVLQBytes`.


it("should add properties successfully", async () => {
    expect(
      await overrideSvgAttributes("<SVG></SVG>", {
        height: null,
        width: null,
        "aria-hidden": true,
        "aria-label": null,
        viewBox: "0 0 2712 894",
      }),
    ).toBe('<SVG aria-hidden="true" viewBox="0 0 2712 894"></SVG>');

    await fc.assert(
      fc.asyncProperty(
        svgArbitrary,
        SVGAttributesArbitrary,
        fc.context(),
        async (svgSource, overrides, ctx) => {
          ctx.log(svgSource);
          const transformedSource = await overrideSvgAttributes(
            svgSource,
            overrides,
          );
          ctx.log(transformedSource);
          expect(transformedSource).toBeTruthy();
          // every truthy override should exist in the transformed source
          Object.entries(overrides)
            .filter(([, value]) => {
              ctx.log(`${value}, ${!!value}`);
              return !!value;
            })
            .forEach(([override]) => {
              expect(transformedSource).toContain(override);
            });
        },
      ),
    );
  })

Summary: The function `overrideSvgAttributes` should correctly add and retain truthy attributes within an SVG string when given attribute overrides.


it("should never throw", async () => {
    await fc.assert(
      fc.asyncProperty(
        fc.string({ unit: "grapheme-composite" }),
        async (input) => {
          expect(await parse(input)).toBeTruthy();
        },
      ),
    );
  })

Summary: The function `parse` should always return a truthy value without throwing an error, regardless of the input string.


test("all-ops", (t) => {
    fc.assert(
        fc.property(
            fc.array(
                fc.nat().map((v) => v * 2 ** -32),
                { minLength: 10 }
            ),
            (values) => {
                const nbDel = Math.floor(values.length / 4)
                const nbSub = Math.floor(values.length / 4)

                const treeList = AvlList.empty<number>()
                const expected: number[] = []

                values.forEach((randVal, i) => {
                    const index = asU32Between(0, treeList.length + 1, randVal)
                    const insVal = i
                    treeList.insert(index, insVal)
                    expected.splice(index, 0, insVal)
                })

                values.slice(0, nbDel).forEach((randVal) => {
                    const index = asU32Between(0, treeList.length, randVal)
                    treeList.delete(index)
                    expected.splice(index, 1)
                })

                values.slice(0, nbSub).forEach((randVal, i) => {
                    const index = asU32Between(0, treeList.length, randVal)
                    const subVal = values.length + i
                    treeList.replace(index, subVal)
                    expected.splice(index, 1, subVal)
                })

                t.deepEqual(treeList.toArray(), expected)
                t.ok(
                    isBalanced(
                        (treeList as unknown as { root: AvlNode<unknown> }).root
                    )
                )
            }
        )
    )
})

Summary: The operations on the AVL list (insertions, deletions, and replacements) should result in an array matching the expected sequence, and the resulting AVL tree must maintain its balanced property.


it('can fill', () => {
    let g = new Goma1015()
    //check definition
    expect(g.fill).toBeDefined()
    expect(g.water).toBeDefined()

    //filling needs to be pot opened
    expect(() => g.fill(15)).toThrowError(/should be OPEN/)
    expect(g.water()).toBe(0)

    //negative water
    g.open()
    expect(() => g.fill(-1)).toThrowError(/can't be filled with negative number/)
    expect(g.water()).toBe(0)

    //if full can not be filled water anymore
    //fill water 1,000 ml
    g.fill(1000)
    expect(g.water()).toBe(1000)
    expect(() => g.fill(1)).toThrowError(/is full/)

    //property based testing for fill
    g = new Goma1015()
    g.open()
    g.plugIn()
    g.plugOff()
    let water = 0
    fc.assert(
      fc.property(fc.nat(1000), w => {
        // to be full
        if (water + w > 1000) {
          expect(() => g.fill(w)).toThrowError(/is full/)
          return
        }
        g.fill(w)
        water += w
        // confirm water volume
        expect(g.water()).toBe(water)
      }),
    )
  })

Summary: The property's hold tests filling the `Goma1015` with water up to 1000 ml while ensuring overfilling results in an error.


it('can dispense', () => {
    let g = new Goma1015()
    //check definition
    expect(g.dispense).toBeDefined()
    expect(g.plugIn).toBeDefined()
    expect(g.plugOff).toBeDefined()

    //deault plugOff
    expect(() => g.dispense(15)).toThrowError(/should be IDLE or KEEP/)
    //dispenseing needs to be pot closed
    g.open()
    g.plugIn()
    g.plugIn()
    expect(() => g.dispense(15)).toThrowError(/should be IDLE or KEEP/)
    g.close()

    //dispenseing needs to be pot plugged
    g.plugOff()
    g.plugOff()
    expect(() => g.dispense(15)).toThrowError(/should be IDLE or KEEP/)
    g.plugIn()

    //negative sec
    expect(() => g.dispense(-1)).toThrowError(/can't be dispensed with negative sec/)
    //can not dispense water anymore if empty
    expect(g.dispense(100)).toBe(0)

    //property based testing for dispense
    g = new Goma1015()
    //fill to full
    let water = 1000
    g.plugIn()
    g.open()
    g.fill(water)
    g.close()
    fc.assert(
      fc.property(fc.nat(10), fc.nat(1000), (s, w) => {
        water = g.water()
        // if it doens't have enough water to be IDLE
        if (water < 10) {
          expect(g.state()).toBe(State.ON_IDLE)
          //water is dispenseed 10 ml/sec
          //means - 10ml
          expect(g.dispense(1)).toBe(water)
          //refill
          g.open()
          g.fill(w)
          g.close()
          return
        }
        //it is active
        if (g.state() === State.ON_ACTIVE_BOIL) {
          //after 1 sec the temp should be 25 > and < 100
          advanceBy(1000)
          expect(g.temperature() > 25).toBe(true)
          expect(g.temperature() < 100).toBe(true)
          //temp should be 100 after 60000 sec passed
          advanceBy(59000)
          expect(g.temperature() == 100).toBe(true)
          //temp should be 100 after 60001 sec passed
          advanceBy(1)
          expect(g.temperature() == 100).toBe(true)
        }
        //it should be KEEP
        expect(g.state()).toBe(State.ON_ACTIVE_KEEP)
        if (s > 0) {
          expect(g.dispense(s)).not.toBe(0)
        }
      }),
    )
    //clear Date.now()
    clear()
  })

Summary: The `Goma1015` can dispense water when certain conditions are met: the pot is closed, plugged in, and in the correct state. During property-based testing, it also verifies that water is dispensed based on available water and system state, and that temperature changes appropriately after specific time advances. Additionally, it validates that negative dispense times throw an error.


it('calculates trivial results correctly', () => {
    fc.assert(
      fc.property(fc.integer({ min: 1, max: 1000000 }), (param) => {
        const result = binomialCoefficient(param, param);
        expect(result).toBe(1);
      }),
    );
  })

Summary: Calculating the binomial coefficient where both parameters are equal returns 1.


it('calculates null result correctly', () => {
    fc.assert(
      fc.property(fc.integer({ min: 1, max: 1000000 }), (param) => {
        const result = binomialCoefficient(param, 0);
        expect(result).toBe(1);
      }),
    );
  })

Summary: Calculating the binomial coefficient with any positive integer and 0 should return 1.


it('calculates unit result correctly', () => {
    fc.assert(
      fc.property(fc.integer({ min: 1, max: 1000000 }), (param) => {
        const result = binomialCoefficient(param, 1);
        expect(result).toBe(param);
      }),
    );
  })

Summary: `binomialCoefficient` with a second parameter of 1 should return the first parameter value.


it('is symmetrical', () => {
    fc.assert(
      fc.property(
        fc.integer({ min: 3, max: 1000000 }),
        fc.integer({ min: 1, max: 500000 }),
        (distribution, offset) => {
          const mid = Math.ceil(distribution / 2);
          fc.pre(offset < mid);
          const a = binomialCoefficient(distribution, offset);
          const b = binomialCoefficient(distribution, distribution - offset);
          expect(a).toEqual(b);
        },
      ),
    );
  })

Summary: The binomial coefficient for given values is symmetrical, meaning it should be equal whether computed with an offset or its complement.


it('calculates dice average correctly', () => {
    fc.assert(
      fc.property(
        // sides
        fc.integer({ min: 1, max: 1000000 }),
        // count
        fc.integer({ min: 1, max: 1000000 }),
        (sides, count) => {
          const dice = new SimpleDiceGroup(sides, count);

          expect(dice.statProps.average).toBe((count * (sides + 1)) / 2);
        },
      ),
    );
  })

Summary: The calculated average of a `SimpleDiceGroup` should equal \((\text{count} \times (\text{sides} + 1)) / 2\).


it('calculates dice minimum correctly', () => {
    fc.assert(
      fc.property(
        // count
        fc.integer({ min: 1, max: 1000000 }),
        // sides
        fc.integer({ min: 1, max: 1000000 }),
        (sides, count) => {
          const dice = new SimpleDiceGroup(sides, count);

          expect(dice.statProps.min).toBe(count);
        },
      ),
    );
  })

Summary: `SimpleDiceGroup` calculates the minimum value as the count of dice.


it('it parses modifiers correctly', () => {
    const modifiers = ['!', '!!', '!p', 'r', 'ro', 'd', 'k', 'dh', 'dl', 'kh', 'kl'];
    fc.assert(
      fc.property(
        fc.nat({ max: modifiers.length - 1 }),
        // number of dice
        fc.integer({ min: 1, max: 1000000 }),
        // number of sides
        fc.integer({ min: 1, max: 1000000 }),
        (index, count, sides) => {
          const parseText = `${count}d${sides}${modifiers[index]}`;
          const parseResult = parseDice(parseText);
          expect(isModifier(parseResult)).toBe(true);
        },
      ),
    );
  })

Summary: Ensures `parseDice` correctly identifies strings with any listed modifier as `Modifier` objects.


it('it parses magnitude modifiers correctly', () => {
    const modifiers = ['d', 'k', 'dh', 'dl', 'kh', 'kl'];
    fc.assert(
      fc.property(
        fc
          .record({
            mod: fc.nat({ max: modifiers.length - 1 }),
            // number of dice
            count: fc.integer({ min: 1, max: 1000000 }),
            // number of sides
            sides: fc.integer({ min: 1, max: 1000000 }),
            magnitude: fc.integer({ min: 1, max: 1000000 }),
          })
          .filter(({ count, magnitude }) => count <= magnitude),
        ({ mod, count, sides, magnitude }) => {
          const parseText = `${count}d${sides}${modifiers[mod]}${magnitude}`;
          const parseResult = parseDice(parseText);
          expect(isModifier(parseResult)).toBe(true);
        },
      ),
    );
  })

Summary: Parsing a dice expression with magnitude modifiers results in `parseDice` returning an object identified as a `Modifier`.


it('it parses target modifiers correctly', () => {
    const modifiers = ['!', '!!', '!p'];
    const comparison = ['', '<', '>', '<=', '>=', '='];
    fc.assert(
      fc.property(
        fc
          .record({
            mod: fc.nat({ max: modifiers.length - 1 }),
            comp: fc.nat({ max: comparison.length - 1 }),
            // number of dice
            count: fc.integer({ min: 1, max: 1000000 }),
            // number of sides
            sides: fc.integer({ min: 1, max: 1000000 }),
            target: fc.integer({ min: 1, max: 1000000 }),
          })
          .filter(({ sides, target }) => sides <= target),
        ({ mod, comp, count, sides, target }) => {
          const parseText = `${count}d${sides}${modifiers[mod]}${comparison[comp]}${target}`;
          const parseResult = parseDice(parseText);
          expect(isModifier(parseResult)).toBe(true);
        },
      ),
    );
  })

Summary: The property confirms that the `parseDice` function correctly parses dice notations with target modifiers, resulting in an output identified as a modifier.


it('parses simple dice specs', () => {
    fc.assert(
      fc.property(
        // number of dice
        fc.integer({ min: 1, max: 1000000 }),
        // number of sides
        fc.integer({ min: 1, max: 1000000 }),
        (n, s) => {
          const parseText = `${n}d${s}`;
          const parseResult = parse(parseText);
          expect(parseResult.lexErrors).toHaveLength(0);
          expect(parseResult.parseErrors).toHaveLength(0);
        },
      ),
    );
  })

Summary: Parses simple dice specifications without lexical or parsing errors for input formats like 'NdS', where N and S are integers.


it('calculates dice average correctly', () => {
    fc.assert(
      fc.property(
        // sides
        fc.integer({ min: 1, max: 1000000 }),
        // count
        fc.integer({ min: 1, max: 1000000 }),
        (sides, count) => {
          const sidesGen = new Constant(sides);
          const countGen = new Constant(count);
          const dice = new PolyDiceGroup(sidesGen, countGen);

          expect(dice.statProps.average).toBe(
            (countGen.statProps.max * (sidesGen.statProps.max + 1)) / 2,
          );
        },
      ),
    );
  })

Summary: Calculates the average of dice rolls based on the number of sides and count, ensuring it matches the expected statistical formula.


it('parses simple modifier expressions', () => {
    const modifiers = ['', '!', '!!', '!p', 'r', 'ro', 'd', 'k', 'dh', 'dl', 'kh', 'kl'];
    fc.assert(
      fc.property(
        fc.nat({ max: modifiers.length - 1 }),
        // number of dice
        fc.integer({ min: 1, max: 1000000 }),
        // number of sides
        fc.integer({ min: 1, max: 1000000 }),
        (index, count, sides) => {
          const parseText = `${count}d${sides}${modifiers[index]}`;
          const parseResult = parse(parseText);
          expect(parseResult.lexErrors).toHaveLength(0);
          expect(parseResult.parseErrors).toHaveLength(0);
        },
      ),
    );
  })

Summary: `parse` successfully parses expressions for dice rolls with various valid modifiers without producing lex or parse errors.


it('parses magnitude modifiers with values', () => {
    const modifiers = ['d', 'k', 'dh', 'dl', 'kh', 'kl'];
    fc.assert(
      fc.property(
        fc
          .record({
            mod: fc.nat({ max: modifiers.length - 1 }),
            // number of dice
            count: fc.integer({ min: 1, max: 1000000 }),
            // number of sides
            sides: fc.integer({ min: 1, max: 1000000 }),
            magnitude: fc.integer({ min: 1, max: 1000000 }),
          })
          .filter(({ count, magnitude }) => count <= magnitude),
        ({ mod, count, sides, magnitude }) => {
          const parseText = `${count}d${sides}${modifiers[mod]}${magnitude}`;
          const parseResult = parse(parseText);
          expect(parseResult.lexErrors).toHaveLength(0);
          expect(parseResult.parseErrors).toHaveLength(0);
        },
      ),
    );
  })

Summary: Parses text input with magnitude modifiers and values without any lexical or parsing errors.


it('parses target modifiers with values', () => {
    const modifiers = ['!', '!!', '!p'];
    const comparison = ['', '<', '>', '<=', '>=', '='];
    fc.assert(
      fc.property(
        fc
          .record({
            mod: fc.nat({ max: modifiers.length - 1 }),
            comp: fc.nat({ max: comparison.length - 1 }),
            // number of dice
            count: fc.integer({ min: 1, max: 1000000 }),
            // number of sides
            sides: fc.integer({ min: 1, max: 1000000 }),
            target: fc.integer({ min: 1, max: 1000000 }),
          })
          .filter(({ sides, target }) => sides <= target),
        ({ mod, comp, count, sides, target }) => {
          const parseText = `${count}d${sides}${modifiers[mod]}${comparison[comp]}${target}`;
          const parseResult = parse(parseText);
          expect(parseResult.lexErrors).toHaveLength(0);
          expect(parseResult.parseErrors).toHaveLength(0);
        },
      ),
    );
  })

Summary: Parses dice expressions with target modifiers and comparison values into valid, error-free parse results.


it('Modifies the counts of the mod', () => {
    fc.assert(
      fc.property(
        // sides
        fc.integer({ min: 1, max: 1000000 }),
        // count & drop: these are dependent
        // we shouldn't be testing if we can drop more than the count - that should be an error
        fc
          .integer({ min: 1, max: 1000000 })
          .chain((count) => fc.tuple(fc.constant(count), fc.nat({ max: count }))),
        // drop mode
        fc.boolean(),
        (sides, countDrop, lowHigh) => {
          const sidesGen = new Constant(sides);
          const countGen = new Constant(countDrop[0]);
          const dropGen = new Constant(countDrop[1]);
          const baseDice = new PolyDiceGroup(sidesGen, countGen);
          const drop = new Drop(baseDice, dropGen, lowHigh ? KeepDropMode.Low : KeepDropMode.High);
          drop.rollGroup();
          expect(drop.currentCount).toBe(countGen.value() - dropGen.value());
        },
      ),
    );
  })

Summary: The `Drop` modifier correctly adjusts the dice count by subtracting the specified drop amount from the original count.


it('calculates the fudge dice average correctly', () => {
    fc.assert(
      fc.property(fc.integer({ min: 1, max: 1000000 }), (count) => {
        const dice = new FudgeDiceGroup(count);
        // interestingly, the average roll on any fudge die is 0
        expect(dice.statProps.average).toBe(0);
      }),
    );
  })

Summary: The average roll of any `FudgeDiceGroup` should be 0, regardless of the number of dice.


it('should throw an assertion error if the number of dimensions is less than 1, infinite, or NaN', () => {
    fc.assert(
      fc.property(invalidDimensions(), dimensions => {
        expect(() => nShell(dimensions)).toThrow()
      }),
    )
  })

Summary: An assertion error is thrown if `nShell` is called with a number of dimensions that is less than 1, infinite, or NaN.


it('should throw an assertion error if the radius range is infinite, inverted (max <= min), or negative in polarity (max < 0)', () => {
    fc.assert(
      fc.property(
        validDimensions(),
        invalidRadiusRange(),
        (dimensions, radiusRange) => {
          expect(() => nShell(dimensions, radiusRange)).toThrow()
        },
      ),
    )
  })

Summary: `nShell` throws an error if the radius range is infinite, inverted (max ≤ min), or negative (max < 0).


it('calculates dice minimum from constant generators correctly', () => {
    fc.assert(
      fc.property(
        // count
        fc.integer({ min: 1, max: 1000000 }),
        // sides
        fc.integer({ min: 1, max: 1000000 }),
        (sides, count) => {
          const sidesGen = new Constant(sides);
          const countGen = new Constant(count);
          const dice = new PolyDiceGroup(sidesGen, countGen);

          expect(dice.statProps.min).toBe(count);
        },
      ),
    );
  })

Summary: Calculates the minimum value of a dice roll from constant generators to be equal to the count.


it('calculates dice maximum correctly', () => {
    fc.assert(
      fc.property(
        // count
        fc.integer({ min: 1, max: 1000000 }),
        // sides
        fc.integer({ min: 1, max: 1000000 }),
        (sides, count) => {
          const sidesGen = new Constant(sides);
          const countGen = new Constant(count);
          const dice = new PolyDiceGroup(sidesGen, countGen);

          expect(dice.statProps.max).toBe(count * sides);
        },
      ),
    );
  })

Summary: `PolyDiceGroup` calculates the maximum possible dice roll as the product of the number of sides and the count of dice.


it("should behave the same way when setting values with indexes", () => {
    fc.assert(
      fc.property(
        fc.array(fc.tuple(fc.integer({ max: 1000, min: -100 }), fc.float()), {
          maxLength: 100,
        }),
        (testData) => {
          let immList = Immutable.List();
          let list = List.empty();
          testData.forEach(([index, value]) => {
            immList = immList.set(index, value);
            list = list.set(index, value);
          });
          expect([...list]).toEqual(immList.toJS());
        }
      )
    );
  })

Summary: Setting values using indexes should produce identical results for both `Immutable.List` and `List.empty()`.


it("should behave the same way when slicing", () => {
    fc.assert(
      fc.property(
        fc.tuple(fc.array(fc.string(), { minLength: 320 }), fc.nat(), fc.nat())
        ,
        (testData) => {
          let immList = Immutable.List.of(...testData[0]).slice(testData[1], testData[1] + testData[2]);
          let list = List.of(...testData[0]).slice(testData[1], testData[1] + testData[2]);

          expect(list.toJS()).toEqual(immList.toJS());
        }
      )
    , { numRuns: 100 });
  })

Summary: Slicing should produce identical results for both `Immutable.List` and `List` when converting to JavaScript arrays.


it('should be a Generator that returns an array of two numbers', () => {
      expect(boxMullerTransform).toBeInstanceOf(Generator)
      fc.assert(
        fc.property(
          prn<any>(fc.oneof(jsf32(), mulberry32(), sfc32(), splitmix32())), // eslint-disable-line @typescript-eslint/no-explicit-any
          state => {
            const [nextState, nextValue] = boxMullerTransform.run(state)
            expect(nextState).not.toBe(state)
            expect(nextValue).toBeInstanceOf(Array)
            expect(nextValue.length).toBe(2)
            const [first, second] = nextValue
            expect(typeof first).toBe('number')
            expect(first).not.toBeNaN()
            expect(typeof second).toBe('number')
            expect(second).not.toBeNaN()
          },
        ),
      )
    })

Summary: `boxMullerTransform` is confirmed to be a `Generator` that, when executed, produces a new state and an array of two valid numbers.


it('should generate points in the unit sphere if no radius range is specified', () => {
    fc.assert(
      fc.property(
        validDimensions(),
        prn<any>(fc.oneof(jsf32(), mulberry32(), sfc32(), splitmix32())), // eslint-disable-line @typescript-eslint/no-explicit-any
        (dimensions, seed) => {
          const generator = nShell(dimensions)
          const [, vector] = generator.run(seed)
          expect(vector.length).toBe(Math.floor(dimensions))
          const m2 = magnitudeSquared(vector)
          expect(m2).toBeGreaterThanOrEqual(0)
          expect(m2).toBeLessThanOrEqual(1)
        },
      ),
    )
  })

Summary: Generated points should lie within the unit sphere when no radius range is specified, with vector length matching the floor of the specified dimensions and magnitude squared between 0 and 1.