diff --git a/src/features/dataset/index.spec.ts b/src/features/dataset/index.spec.ts index fc48953..83d456c 100644 --- a/src/features/dataset/index.spec.ts +++ b/src/features/dataset/index.spec.ts @@ -54,6 +54,7 @@ describe('dataset', () => { }); it('update if the source value changes', () => { + // Arrange const [data, setData] = createSignal([ { id: '1', name: 'a first name', amount: 30 }, { id: '2', name: 'a second name', amount: 20 }, @@ -61,28 +62,25 @@ describe('dataset', () => { ]); const dataset = createDataSet(data); - dataset.mutateEach(item => ({ ...item, amount: item.amount * 2 })) + dataset.mutateEach(item => ({ ...item, amount: item.amount * 2 })); + // Act + setData([ + { id: '4', name: 'a first name', amount: 30 }, + { id: '5', name: 'a second name', amount: 20 }, + { id: '6', name: 'a third name', amount: 10 }, + ]); + + // Assert return testEffect(done => - createEffect((run: number = 0) => { - data(); + createEffect(() => { + expect(dataset.value).toEqual([ + { id: '4', name: 'a first name', amount: 60 }, + { id: '5', name: 'a second name', amount: 40 }, + { id: '6', name: 'a third name', amount: 20 }, + ]) - if (run === 0) { - setData([ - { id: '4', name: 'a first name', amount: 30 }, - { id: '5', name: 'a second name', amount: 20 }, - { id: '6', name: 'a third name', amount: 10 }, - ]); - } else if (run === 1) { - expect(dataset.value).toEqual([ - { id: '4', name: 'a first name', amount: 60 }, - { id: '5', name: 'a second name', amount: 40 }, - { id: '6', name: 'a third name', amount: 20 }, - ]) - - done() - } - return run + 1 + done() }) ); }); diff --git a/src/features/source/source.spec.ts b/src/features/source/source.spec.ts new file mode 100644 index 0000000..5d5a4a4 --- /dev/null +++ b/src/features/source/source.spec.ts @@ -0,0 +1,49 @@ +import { describe, expect } from "vitest"; +import { createSource } from "./source"; +import { it } from "~/test-helpers"; +import { testEffect } from "@solidjs/testing-library"; +import { createEffect, createSignal } from "solid-js"; + +describe('Source', () => { + describe('Source', () => { + it('should return a `Source`', () => { + // Arrange + + // Act + const actual = createSource(''); + + // Assert + expect(actual.out).toBe(''); + }); + + it('should transform the input format to output format', () => { + // Arrange + const given = '**text**\n'; + const expected = '

text

'; + + // Act + const actual = createSource(given); + + // Assert + expect(actual.out).toBe(expected); + }); + + it('should contain query results', () => { + // Arrange + const expected: [number, number][] = [[8, 9], [12, 13], [15, 16]]; + const source = createSource('this is a seachable string'); + + // Act + source.query = 'a'; + + // Assert + return testEffect(done => { + createEffect(() => { + expect(source.queryResults).toEqual(expected); + + done() + }); + }); + }); + }); +}); \ No newline at end of file diff --git a/src/features/source/source.ts b/src/features/source/source.ts index f5d2fcc..439f5e8 100644 --- a/src/features/source/source.ts +++ b/src/features/source/source.ts @@ -37,16 +37,12 @@ const inToOutProcessor = unified().use(remarkParse).use(remarkRehype).use(rehype const outToInProcessor = unified().use(rehypeParse).use(rehypeRemark).use(remarkStringify, { bullet: '-' }); export function createSource(initalValue: string): Source { - const [store, setStore] = createStore({ in: initalValue, out: '', plain: '', query: '', metadata: { spellingErrors: [], grammarErrors: [], queryResults: [] } }); - onMount(() => { - const ast = inToOutProcessor.runSync(inToOutProcessor.parse(initalValue)); + const ast = inToOutProcessor.runSync(inToOutProcessor.parse(initalValue)); + const out = String(inToOutProcessor.stringify(ast)); + const plain = String(unified().use(plainTextStringify).stringify(ast)); - setStore({ - out: String(inToOutProcessor.stringify(ast)), - plain: String(unified().use(plainTextStringify).stringify(ast)), - }); - }); + const [store, setStore] = createStore({ in: initalValue, out, plain, query: '', metadata: { spellingErrors: [], grammarErrors: [], queryResults: [] } }); createEffect(() => { const value = store.plain; diff --git a/src/utilities.spec.ts b/src/utilities.spec.ts index 2a1bc3e..ee7cb90 100644 --- a/src/utilities.spec.ts +++ b/src/utilities.spec.ts @@ -1,23 +1,9 @@ -import { afterAll, beforeEach, describe, expect, vi } from 'vitest'; +import { describe, expect, vi } from 'vitest'; import { decode, deepCopy, deepDiff, filter, gen__split_by_filter, map, MutarionKind, split_by_filter, splitAt } from './utilities'; -import { install } from '@sinonjs/fake-timers'; import { it } from '~/test-helpers'; const { spyOn } = vi; -type MilliSeconds = number; -const useFakeTimers = () => { - const clock = install(); - - beforeEach(() => clock.reset()); - afterAll(() => clock.uninstall()); - - return { - tick(timeToAdvance: MilliSeconds) { - clock.tick(timeToAdvance); - }, - }; -}; const first = (iterable: Iterable): T | undefined => { for (const value of iterable) { return value; @@ -126,6 +112,18 @@ describe('utilities', () => { expect(actual).toBe(expected); }); + it('should decode \\b characters', async () => { + // Arrange + const given = 'this is\\ba string'; + const expected = 'this is\ba string'; + + // Act + const actual = decode(given); + + // Assert + expect(actual).toBe(expected); + }); + it('should decode \\n characters', async () => { // Arrange const given = 'this is\\na string'; @@ -138,6 +136,54 @@ describe('utilities', () => { expect(actual).toBe(expected); }); + it('should decode \\r characters', async () => { + // Arrange + const given = 'this is\\ra string'; + const expected = 'this is\ra string'; + + // Act + const actual = decode(given); + + // Assert + expect(actual).toBe(expected); + }); + + it('should decode \\f characters', async () => { + // Arrange + const given = 'this is\\fa string'; + const expected = 'this is\fa string'; + + // Act + const actual = decode(given); + + // Assert + expect(actual).toBe(expected); + }); + + it('should decode \' characters', async () => { + // Arrange + const given = 'this is\\\'a string'; + const expected = 'this is\'a string'; + + // Act + const actual = decode(given); + + // Assert + expect(actual).toBe(expected); + }); + + it('should decode \" characters', async () => { + // Arrange + const given = 'this is\"a string'; + const expected = 'this is"a string'; + + // Act + const actual = decode(given); + + // Assert + expect(actual).toBe(expected); + }); + it('should decode \\uHHHH characters', async () => { // Arrange const given = 'this is \\u1234 a string'; @@ -301,6 +347,18 @@ describe('utilities', () => { expect(actual).toEqual({ kind: MutarionKind.Create, key: 'key', value: 'value' }); }); + it('should yield a mutation of type create when `b` contains a value that `a` does not', async () => { + // arrange + const a = { key: null }; + const b = { key: 'value' }; + + // Act + const actual = first(deepDiff(a, b)); + + // Arrange + expect(actual).toEqual({ kind: MutarionKind.Create, key: 'key', value: 'value' }); + }); + it('should yield a mutation of type delete when `a` contains a key that `b` does not', async () => { // arrange const a = { key: 'value' }; @@ -313,6 +371,18 @@ describe('utilities', () => { expect(actual).toEqual({ kind: MutarionKind.Delete, key: 'key', original: 'value' }); }); + it('should yield a mutation of type delete when `a` contains a key that `b` does not', async () => { + // arrange + const a = { key: 'value' }; + const b = { key: undefined }; + + // Act + const actual = first(deepDiff(a, b)); + + // Arrange + expect(actual).toEqual({ kind: MutarionKind.Delete, key: 'key', original: 'value' }); + }); + it('should yield a mutation of type update when the value of a key in `a` is not equal to the value of the same key in `b`', async () => { // arrange const a = { key: 'old' }; diff --git a/src/utilities.ts b/src/utilities.ts index 024898f..421d8a0 100644 --- a/src/utilities.ts +++ b/src/utilities.ts @@ -27,8 +27,11 @@ export function split_by_filter(subject: string, filter: string): (readonly [boo return Array.from(gen__split_by_filter(subject, filter)); } +type Hex = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 'a' | 'b' | 'c' | 'd' | 'e' | 'f'; +type EncodedChar = 't' | 'b' | 'n' | 'r' | 'f' | '\'' | '"' | `u${Hex}${Hex | ''}${Hex | ''}${Hex | ''}` + const decodeRegex = /(? ({ +const decodeReplacer = (_: any, char: EncodedChar) => ({ t: '\t', b: '\b', n: '\n', @@ -37,7 +40,7 @@ const decodeReplacer = (_: any, char: string) => ({ "'": '\'', '"': '\"', u: String.fromCharCode(Number.parseInt(`0x${char.slice(1)}`)), -}[char.charAt(0)] ?? ''); +}[char.charAt(0) as ('t' | 'b' | 'n' | 'r' | 'f' | '\'' | '"' | 'u')]); export const decode = (subject: string): string => subject.replace(decodeRegex, decodeReplacer); export const deepCopy = (original: T): T => { @@ -110,8 +113,13 @@ export function* deepDiff(a: T1, b: T2, pa const key = path.concat(keyA!.toString()).join('.'); yield ((): Mutation => { - if (valueA === null || valueA === undefined) return { key, kind: MutarionKind.Create, value: valueB }; - if (valueB === null || valueB === undefined) return { key, kind: MutarionKind.Delete, original: valueA }; + if (valueA === null || valueA === undefined) { + return { key, kind: MutarionKind.Create, value: valueB }; + } + + if (valueB === null || valueB === undefined) { + return { key, kind: MutarionKind.Delete, original: valueA }; + } return { key, kind: MutarionKind.Update, value: valueB, original: valueA }; })(); @@ -200,7 +208,7 @@ const bufferredIterator = (subject: I const next = () => { const res = iterator.next(); - done = res.done ?? false; + done = res.done!; if (!done) { buffer.push(res.value);