diff --git a/src/content-modules/Browse.svelte b/src/content-modules/Browse.svelte index 3e78bfd4..5b6e2f2b 100644 --- a/src/content-modules/Browse.svelte +++ b/src/content-modules/Browse.svelte @@ -8,7 +8,6 @@ import type { LintResult } from "../lib/ColorLint"; import MiniPalPreview from "../components/MiniPalPreview.svelte"; - import LintCustomizationModal from "../linting/LintCustomizationModal.svelte"; import { lint } from "../lib/api-calls"; import Tooltip from "../components/Tooltip.svelte"; import { buttonStyle } from "../lib/styles"; @@ -266,17 +265,3 @@ {/each} - -{#if $lintStore.focusedLint !== false} - { - setTimeout(() => { - // loadLints() - // .then(() => lint(currentPal)) - // .then((res) => { - // checks = res; - // }); - }, 100); - }} - /> -{/if} diff --git a/src/lib/ColorLint.ts b/src/lib/ColorLint.ts index e7f3d20d..1aa4d52b 100644 --- a/src/lib/ColorLint.ts +++ b/src/lib/ColorLint.ts @@ -2,18 +2,18 @@ import type { Palette, PalType, Affect, Context } from "../types"; export type LintLevel = "error" | "warning"; export interface LintResult { - name: string; - passes: boolean; - message: string; - level: LintLevel; - group: string; - description: string; - isCustom: false | string; - taskTypes: PalType[]; affectTypes: Affect[]; contextTypes: Context[]; - subscribedFix: string; + description: string; + group: string; + isCustom: false | string; + level: LintLevel; + message: string; + name: string; naturalLanguageProgram: string; + passes: boolean; + subscribedFix: string; + taskTypes: PalType[]; } export class ColorLint { diff --git a/src/lib/CustomLint.ts b/src/lib/CustomLint.ts index 5e917ecb..318378c5 100644 --- a/src/lib/CustomLint.ts +++ b/src/lib/CustomLint.ts @@ -1,5 +1,5 @@ import { ColorLint } from "./ColorLint"; -import type { PalType, Affect, Context } from "../types"; +import type { PalType, Affect, Context, Palette } from "../types"; import { LLEval, prettyPrintLL, @@ -20,6 +20,8 @@ export interface CustomLint { program: string; subscribedFix?: string; taskTypes: PalType[]; + expectedPassingTests: Palette[]; + expectedFailingTests: Palette[]; } export function CreateCustomLint(props: CustomLint) { diff --git a/src/lib/lints/affects.ts b/src/lib/lints/affects.ts index 02a4d9bf..1bb8bf8d 100644 --- a/src/lib/lints/affects.ts +++ b/src/lib/lints/affects.ts @@ -58,6 +58,8 @@ const lint1: CustomLint = { failMessage: `This palette does not have at least one light blue, beige, or gray, which may not be appropriate for a playful palette. In particular {{blame}} may be problematic.`, id: `light-blues-beiges-grays-playful-built-in`, blameMode: "single", + expectedPassingTests: [], + expectedFailingTests: [], }; lints.push(lint1); @@ -88,6 +90,8 @@ const lint2: CustomLint = { failMessage: `This palette has dark reds or browns, which may not be appropriate for a positive palette. In particular {{blame}} may be problematic.`, id: `dark-reds-browns-positive-built-in`, blameMode: "single", + expectedPassingTests: [], + expectedFailingTests: [], }; lints.push(lint2); @@ -118,6 +122,8 @@ const lint3: CustomLint = { failMessage: `This palette has light colors, particularly greens, which may not be appropriate for a negative palette. In particular {{blame}} may be problematic.`, id: `light-colors-greens-negative-built-in`, blameMode: "single", + expectedPassingTests: [], + expectedFailingTests: [], }; lints.push(lint3); @@ -150,6 +156,8 @@ lints.push(lint3); // failMessage: `This palette does not have two thematic strategies (blue-gray, green-gray) bridged by a common color (yellow), which may not be appropriate for a trustworthy palette.`, // id: `trustworthy-thematic-strategies-yellow-built-in`, // blameMode: "single", +// expectedPassingTests: [], +// expectedFailingTests: [], // }; // lints.push(lint4); diff --git a/src/lib/lints/avoid-extremes.ts b/src/lib/lints/avoid-extremes.ts index d211db61..1940aca1 100644 --- a/src/lib/lints/avoid-extremes.ts +++ b/src/lib/lints/avoid-extremes.ts @@ -1,4 +1,4 @@ -import { JSONToPrettyString } from "../utils"; +import { JSONToPrettyString, makePalFromString } from "../utils"; import type { CustomLint } from "../CustomLint"; // https://www.sciencedirect.com/science/article/pii/S0167947308005549?casa_token=s8jmZqboaYgAAAAA:7lsAu7YUHVBTQA_eaKJ_3FFGv309684j_NTisGO9mIr3UZNIJ6hlAlxPQo04xzsowG7-dH0vzm4 @@ -29,5 +29,16 @@ const lint: CustomLint = { failMessage: `Colors at either end of the lightness spectrum {{blame}} are hard to discriminate in some contexts, and are sometimes advised against`, id: "extreme-colors-built-in", blameMode: "single", + expectedPassingTests: [makePalFromString(["#ff7e0e"])], + expectedFailingTests: [ + makePalFromString([ + "#000000", + "#ffffff", + "#ff7e0e", + "#00ff00", + "#0084a9", + "#0000ff", + ]), + ], }; export default lint; diff --git a/src/lib/lints/discriminative-power.ts b/src/lib/lints/discriminative-power.ts index e0c947a9..25c1f8f3 100644 --- a/src/lib/lints/discriminative-power.ts +++ b/src/lib/lints/discriminative-power.ts @@ -21,5 +21,7 @@ const lint: CustomLint = { failMessage: ``, id: "discrim-power-built-in", blameMode: "single", + expectedPassingTests: [], + expectedFailingTests: [], }; export default lint; diff --git a/src/lib/lints/ugly-colors.ts b/src/lib/lints/ugly-colors.ts index 6e715ae4..0a15e15c 100644 --- a/src/lib/lints/ugly-colors.ts +++ b/src/lib/lints/ugly-colors.ts @@ -1,4 +1,4 @@ -import { JSONToPrettyString } from "../utils"; +import { JSONToPrettyString, makePalFromString } from "../utils"; import type { CustomLint } from "../CustomLint"; const lint: CustomLint = { @@ -31,5 +31,11 @@ const lint: CustomLint = { group: "design", id: "ugly-colors-built-in", blameMode: "single", + expectedPassingTests: [ + makePalFromString(["#000000", "#FFFFFF", "#FF0000", "#00FF00", "#0000FF"]), + ], + expectedFailingTests: [ + makePalFromString(["#000000", "#0010FF", "#6A7E25", "#00FF00", "#0000FF"]), + ], }; export default lint; diff --git a/src/linting/LintCustomizationPreview.svelte b/src/linting/LintCustomizationPreview.svelte new file mode 100644 index 00000000..612fdea8 --- /dev/null +++ b/src/linting/LintCustomizationPreview.svelte @@ -0,0 +1,111 @@ + + +
+ {#each pal.colors as color, idx} + +
+ { + // @ts-ignore + const val = e.target.value; + const newColors = [...pal.colors]; + newColors[idx] = Color.colorFromString(val, pal.colorSpace); + updatePal({ ...pal, colors: newColors }); + }} + /> + { + updatePal({ + ...pal, + // @ts-ignore + colors: pal.colors.map((x) => x.toColorSpace(newSpace)), + // @ts-ignore + colorSpace: newSpace, + }); + }} + colorMode={pal.colorSpace} + {color} + onColorChange={(newColor) => { + const newColors = [...pal.colors]; + newColors[idx] = newColor; + updatePal({ ...pal, colors: newColors }); + }} + /> + +
+ +
+ {/each} + +
+ + + updatePal({ ...pal, background: newColor })} + bg={pal.background} + colorSpace={pal.colorSpace} + onSpaceChange={(newSpace) => { + updatePal({ + ...pal, + // @ts-ignore + colors: pal.colors.map((x) => x.toColorSpace(newSpace)), + // @ts-ignore + colorSpace: newSpace, + }); + }} + /> +
+ +
+
diff --git a/src/linting/LintCustomizationTab.svelte b/src/linting/LintCustomizationTab.svelte index e46a7fad..9040fcee 100644 --- a/src/linting/LintCustomizationTab.svelte +++ b/src/linting/LintCustomizationTab.svelte @@ -3,17 +3,17 @@ import { BUILT_INS } from "../lib/linter"; import colorStore from "../stores/color-store"; - import Modal from "../components/Modal.svelte"; import MonacoEditor from "../components/MonacoEditor.svelte"; import Nav from "../components/Nav.svelte"; import PalPreview from "../components/PalPreview.svelte"; import { CreateCustomLint } from "../lib/CustomLint"; import { buttonStyle } from "../lib/styles"; - import { JSONStringify } from "../lib/utils"; + import { JSONStringify, makePalFromString } from "../lib/utils"; import type { CustomLint } from "../lib/CustomLint"; import { affects, contexts } from "../types"; - - // export let onClose: () => void; + import type { Palette } from "../types"; + import type { LintResult } from "../lib/ColorLint"; + import LintCustomizationPreview from "./LintCustomizationPreview.svelte"; $: lint = $lintStore.lints.find( (lint) => lint.id === $lintStore.focusedLint @@ -24,17 +24,16 @@ $: isBuiltInThatsBeenModified = builtInLint && lint?.program !== builtInLint?.program; - $: currentPal = $colorStore.palettes[$colorStore.currentPal]; // run this lint let errors: any = null; - function runLint(lint: CustomLint, options: any) { + function runLint(lint: CustomLint, options: any, pal: Palette) { if (!lint) { - // onClose(); + lintStore.setFocusedLint(false); return; } try { const customLint = CreateCustomLint(lint); - const result = new customLint(currentPal).run(options); + const result = new customLint(pal).run(options); errors = null; return result; } catch (e) { @@ -42,8 +41,10 @@ } } let debugCompare = false; - $: lintRun = runLint(lint, { debugCompare }); - let showDoubleCheck = false; + let showTestCases = true; + let showDeleteDoubleCheck = false; + $: currentPal = $colorStore.palettes[$colorStore.currentPal]; + $: lintRun = runLint(lint, { debugCompare }, currentPal); $: currentTaskTypes = lint?.taskTypes || ([] as string[]); $: currentAffects = lint?.affectTypes || ([] as string[]); @@ -70,6 +71,27 @@ }, {} as Record ); + + type TestResult = { + pal: Palette; + result: LintResult; + blame: any; + }; + // test results + $: passingTestResults = ( + (showTestCases && lint?.expectedPassingTests) || + [] + ).map((pal) => { + const result = runLint(lint, {}, pal); + return { result, pal, blame: result?.checkData }; + }) as TestResult[]; + $: failingTestResults = ( + (showTestCases && lint?.expectedFailingTests) || + [] + ).map((pal) => { + const result = runLint(lint, {}, pal); + return { result, pal, blame: result?.checkData }; + }) as TestResult[]; {#if !lint} @@ -94,13 +116,6 @@ {/if} {#if lint} -
@@ -123,7 +138,7 @@ {/if}
- {#if showDoubleCheck} + {#if showDeleteDoubleCheck}
Are you sure you want to delete this lint?
{:else} - {/if} @@ -365,7 +383,97 @@ {/if}
-
Test Cases
+
+
Test Cases
+
+ + {#if showTestCases} +
Expected to be passing:
+
+ {#each passingTestResults as passing, idx} +
+ { + const newTests = [...lint.expectedPassingTests].filter( + (_, i) => i !== idx + ); + lintStore.setCurrentLintExpectedPassingTests(newTests); + }} + pal={passing.pal} + blamedSet={new Set(passing.blame)} + updatePal={(newPal) => { + const newTests = [...lint.expectedPassingTests]; + newTests[idx] = newPal; + lintStore.setCurrentLintExpectedPassingTests(newTests); + }} + /> + {#if passing.result?.passes} +
Correct
+ {:else} +
Incorrect
+ {/if} +
+ {/each} + +
+
Expected to be failing:
+
+ {#each failingTestResults as failing, idx} +
+ { + const newTests = [...lint.expectedFailingTests].filter( + (_, i) => i !== idx + ); + lintStore.setCurrentLintExpectedFailingTests(newTests); + }} + pal={failing.pal} + blamedSet={new Set(failing.blame)} + updatePal={(newPal) => { + const newTests = [...lint.expectedFailingTests]; + newTests[idx] = newPal; + lintStore.setCurrentLintExpectedFailingTests(newTests); + }} + /> + {#if !failing.result?.passes} +
Correct
+ {:else} +
Incorrect
+ {/if} +
+ {/each} + +
+ {/if}
Lint Program
@@ -387,5 +495,4 @@ />
- {/if} diff --git a/src/stores/lint-store.ts b/src/stores/lint-store.ts index 9df1ffb8..fcd4375f 100644 --- a/src/stores/lint-store.ts +++ b/src/stores/lint-store.ts @@ -1,7 +1,14 @@ import { writable } from "svelte/store"; import * as idb from "idb-keyval"; import type { CustomLint } from "../lib/CustomLint"; -import type { PalType, Affect, Context } from "../types"; +import type { + PalType, + Affect, + Context, + Palette, + StringPalette, +} from "../types"; +import { Color } from "../lib/Color"; import type { LintResult } from "../lib/ColorLint"; import { JSONStringify } from "../lib/utils"; import { BUILT_INS } from "../lib/linter"; @@ -28,6 +35,51 @@ const builtInIndex = BUILT_INS.reduce((acc, x) => { return acc; }, {} as Record); +function serializePalette(pal: Palette): StringPalette { + return { + ...pal, + background: pal.background.toString(), + colors: pal.colors.map((x) => x.toString()), + }; +} + +function deserializePalette(pal: StringPalette): Palette { + return { + ...pal, + background: Color.colorFromString(pal.background, pal.colorSpace), + colors: pal.colors.map((x) => Color.colorFromString(x, pal.colorSpace)), + }; +} + +function serializeStore(store: StoreData) { + return { + ...store, + lints: store.lints.map((x) => ({ + ...x, + expectedFailingTests: (x.expectedFailingTests || []).map( + serializePalette + ), + expectedPassingTests: (x.expectedPassingTests || []).map( + serializePalette + ), + })), + }; +} +function deserializeStore(store: any) { + return { + ...store, + lints: store.lints.map((x: any) => ({ + ...x, + expectedFailingTests: (x.expectedFailingTests || []).map( + deserializePalette + ), + expectedPassingTests: (x.expectedPassingTests || []).map( + deserializePalette + ), + })), + }; +} + const storeName = "color-pal-lints"; function createStore() { let storeData: StoreData = JSON.parse(JSON.stringify(InitialStore)); @@ -36,21 +88,26 @@ function createStore() { const { subscribe, set, update } = writable(storeData); idb.get(storeName).then((x) => { - let storeBase = { ...InitialStore, ...x }; + let storeBase = deserializeStore({ ...InitialStore, ...x }); let lints = (storeBase.lints || []).map( (x: CustomLint) => builtInIndex[x.id] || x ) as CustomLint[]; const missingBuiltIns = BUILT_INS.filter( (x) => !lints.find((y) => y.id === x.id) - ); + ).map((x) => ({ + ...x, + expectedFailingTests: [], + expectedPassingTests: [], + })); + // TODO reverse these const newStore = { ...storeBase, lints: [...lints, ...missingBuiltIns] }; set(newStore); - idb.set(storeName, newStore); + idb.set(storeName, serializeStore(newStore)); }); const persistUpdate = (updateFunc: (old: StoreData) => StoreData) => update((oldStore) => { const newVal: StoreData = updateFunc(oldStore); - idb.set(storeName, newVal).then(() => { + idb.set(storeName, serializeStore(newVal)).then(() => { loadLints(); }); return newVal; @@ -95,6 +152,10 @@ function createStore() { lintUpdate((old) => ({ ...old, failMessage })), setCurrentLintBlameMode: (blameMode: "pair" | "single" | "none") => lintUpdate((old) => ({ ...old, blameMode })), + setCurrentLintExpectedFailingTests: (expectedFailingTests: Palette[]) => + lintUpdate((old) => ({ ...old, expectedFailingTests })), + setCurrentLintExpectedPassingTests: (expectedPassingTests: Palette[]) => + lintUpdate((old) => ({ ...old, expectedPassingTests })), deleteLint: (id: string) => persistUpdate((old) => ({ ...old, @@ -149,6 +210,8 @@ function newLint(newLintFrag: Partial): CustomLint { failMessage: "v confusing", id: newId(), blameMode: "none", + expectedFailingTests: [...(newLintFrag.expectedFailingTests || [])], + expectedPassingTests: [...(newLintFrag.expectedPassingTests || [])], ...newLintFrag, }; }