Refactor to highlight api #36
5 changed files with 168 additions and 47 deletions
|
@ -54,6 +54,7 @@ describe('dataset', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('update if the source value changes', () => {
|
it('update if the source value changes', () => {
|
||||||
|
// Arrange
|
||||||
const [data, setData] = createSignal([
|
const [data, setData] = createSignal([
|
||||||
{ id: '1', name: 'a first name', amount: 30 },
|
{ id: '1', name: 'a first name', amount: 30 },
|
||||||
{ id: '2', name: 'a second name', amount: 20 },
|
{ id: '2', name: 'a second name', amount: 20 },
|
||||||
|
@ -61,28 +62,25 @@ describe('dataset', () => {
|
||||||
]);
|
]);
|
||||||
const dataset = createDataSet(data);
|
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 =>
|
return testEffect(done =>
|
||||||
createEffect((run: number = 0) => {
|
createEffect(() => {
|
||||||
data();
|
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) {
|
done()
|
||||||
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
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
49
src/features/source/source.spec.ts
Normal file
49
src/features/source/source.spec.ts
Normal file
|
@ -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 = '<p><strong>text</strong></p>';
|
||||||
|
|
||||||
|
// 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()
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -37,16 +37,12 @@ const inToOutProcessor = unified().use(remarkParse).use(remarkRehype).use(rehype
|
||||||
const outToInProcessor = unified().use(rehypeParse).use(rehypeRemark).use(remarkStringify, { bullet: '-' });
|
const outToInProcessor = unified().use(rehypeParse).use(rehypeRemark).use(remarkStringify, { bullet: '-' });
|
||||||
|
|
||||||
export function createSource(initalValue: string): Source {
|
export function createSource(initalValue: string): Source {
|
||||||
const [store, setStore] = createStore<SourceStore>({ 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({
|
const [store, setStore] = createStore<SourceStore>({ in: initalValue, out, plain, query: '', metadata: { spellingErrors: [], grammarErrors: [], queryResults: [] } });
|
||||||
out: String(inToOutProcessor.stringify(ast)),
|
|
||||||
plain: String(unified().use(plainTextStringify).stringify(ast)),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
const value = store.plain;
|
const value = store.plain;
|
||||||
|
|
|
@ -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 { 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';
|
import { it } from '~/test-helpers';
|
||||||
|
|
||||||
const { spyOn } = vi;
|
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 = <T>(iterable: Iterable<T>): T | undefined => {
|
const first = <T>(iterable: Iterable<T>): T | undefined => {
|
||||||
for (const value of iterable) {
|
for (const value of iterable) {
|
||||||
return value;
|
return value;
|
||||||
|
@ -126,6 +112,18 @@ describe('utilities', () => {
|
||||||
expect(actual).toBe(expected);
|
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 () => {
|
it('should decode \\n characters', async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
const given = 'this is\\na string';
|
const given = 'this is\\na string';
|
||||||
|
@ -138,6 +136,54 @@ describe('utilities', () => {
|
||||||
expect(actual).toBe(expected);
|
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 () => {
|
it('should decode \\uHHHH characters', async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
const given = 'this is \\u1234 a string';
|
const given = 'this is \\u1234 a string';
|
||||||
|
@ -301,6 +347,18 @@ describe('utilities', () => {
|
||||||
expect(actual).toEqual({ kind: MutarionKind.Create, key: 'key', value: 'value' });
|
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 () => {
|
it('should yield a mutation of type delete when `a` contains a key that `b` does not', async () => {
|
||||||
// arrange
|
// arrange
|
||||||
const a = { key: 'value' };
|
const a = { key: 'value' };
|
||||||
|
@ -313,6 +371,18 @@ describe('utilities', () => {
|
||||||
expect(actual).toEqual({ kind: MutarionKind.Delete, key: 'key', original: 'value' });
|
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 () => {
|
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
|
// arrange
|
||||||
const a = { key: 'old' };
|
const a = { key: 'old' };
|
||||||
|
|
|
@ -27,8 +27,11 @@ export function split_by_filter(subject: string, filter: string): (readonly [boo
|
||||||
return Array.from<readonly [boolean, string]>(gen__split_by_filter(subject, filter));
|
return Array.from<readonly [boolean, string]>(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 = /(?<!\\)\\(t|b|n|r|f|'|"|u[0-9a-f]{1,4})/gi;
|
const decodeRegex = /(?<!\\)\\(t|b|n|r|f|'|"|u[0-9a-f]{1,4})/gi;
|
||||||
const decodeReplacer = (_: any, char: string) => ({
|
const decodeReplacer = (_: any, char: EncodedChar) => ({
|
||||||
t: '\t',
|
t: '\t',
|
||||||
b: '\b',
|
b: '\b',
|
||||||
n: '\n',
|
n: '\n',
|
||||||
|
@ -37,7 +40,7 @@ const decodeReplacer = (_: any, char: string) => ({
|
||||||
"'": '\'',
|
"'": '\'',
|
||||||
'"': '\"',
|
'"': '\"',
|
||||||
u: String.fromCharCode(Number.parseInt(`0x${char.slice(1)}`)),
|
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 decode = (subject: string): string => subject.replace(decodeRegex, decodeReplacer);
|
||||||
|
|
||||||
export const deepCopy = <T>(original: T): T => {
|
export const deepCopy = <T>(original: T): T => {
|
||||||
|
@ -110,8 +113,13 @@ export function* deepDiff<T1 extends object, T2 extends object>(a: T1, b: T2, pa
|
||||||
const key = path.concat(keyA!.toString()).join('.');
|
const key = path.concat(keyA!.toString()).join('.');
|
||||||
|
|
||||||
yield ((): Mutation => {
|
yield ((): Mutation => {
|
||||||
if (valueA === null || valueA === undefined) return { key, kind: MutarionKind.Create, value: valueB };
|
if (valueA === null || valueA === undefined) {
|
||||||
if (valueB === null || valueB === undefined) return { key, kind: MutarionKind.Delete, original: valueA };
|
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 };
|
return { key, kind: MutarionKind.Update, value: valueB, original: valueA };
|
||||||
})();
|
})();
|
||||||
|
@ -200,7 +208,7 @@ const bufferredIterator = <T extends readonly [string | number, any]>(subject: I
|
||||||
|
|
||||||
const next = () => {
|
const next = () => {
|
||||||
const res = iterator.next();
|
const res = iterator.next();
|
||||||
done = res.done ?? false;
|
done = res.done!;
|
||||||
|
|
||||||
if (!done) {
|
if (!done) {
|
||||||
buffer.push(res.value);
|
buffer.push(res.value);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue