ui: unify practice composer actions and node picker copy
This commit is contained in:
@@ -0,0 +1,56 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import type { ReactNode } from "react";
|
||||||
|
import { DumbbellIcon } from "lucide-react";
|
||||||
|
|
||||||
|
/** Home and the draft share one wrapping action row. Picker controls lead;
|
||||||
|
* setup is centered only when it is the sole action. */
|
||||||
|
export function ComposerActions({ picker, setup }: { picker?: ReactNode; setup?: ReactNode }) {
|
||||||
|
if (!picker && !setup) return null;
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-testid="composer-actions"
|
||||||
|
className={`pointer-events-auto mb-2 flex flex-wrap items-center gap-2 ${picker ? "justify-start" : "justify-center"}`}
|
||||||
|
>
|
||||||
|
{picker}
|
||||||
|
{setup}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Count and Clear stay together if the setup action wraps onto another line.
|
||||||
|
* These are node counts, even when a whole chain supplied the selection. */
|
||||||
|
export function NodePickerControl({ count, onOpen, onClear }: {
|
||||||
|
count: number;
|
||||||
|
onOpen?: () => void;
|
||||||
|
onClear?: () => void;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div className="flex items-center gap-1.5">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
data-testid="practice-props-chip"
|
||||||
|
onClick={onOpen}
|
||||||
|
className={
|
||||||
|
"flex cursor-pointer items-center gap-1.5 rounded-full border px-3 py-1.5 font-ui text-xs transition-colors " +
|
||||||
|
(count > 0
|
||||||
|
? "border-border bg-card text-foreground hover:bg-accent"
|
||||||
|
: "border-dashed border-border/80 bg-transparent text-muted-foreground hover:bg-accent hover:text-foreground")
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<DumbbellIcon aria-hidden="true" className="size-3.5 text-muted-foreground" />
|
||||||
|
{count > 0 ? `${count} ${count === 1 ? "node" : "nodes"} selected` : "Pick nodes"}
|
||||||
|
</button>
|
||||||
|
{count > 0 && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
data-testid="practice-props-clear"
|
||||||
|
onClick={onClear}
|
||||||
|
className="cursor-pointer rounded-full px-2 py-1 font-ui text-xs text-muted-foreground transition-colors hover:bg-accent hover:text-foreground"
|
||||||
|
>
|
||||||
|
Clear
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
+11
-35
@@ -6,6 +6,7 @@ import { CornerDownLeftIcon, DumbbellIcon } from "lucide-react";
|
|||||||
import { GraphView } from "@/components/graph-view";
|
import { GraphView } from "@/components/graph-view";
|
||||||
import { MinimalHome } from "@/components/minimal-home";
|
import { MinimalHome } from "@/components/minimal-home";
|
||||||
import { SetupDialog } from "@/components/setup-dialog";
|
import { SetupDialog } from "@/components/setup-dialog";
|
||||||
|
import { ComposerActions, NodePickerControl } from "@/components/composer-actions";
|
||||||
import { useStrongKeyless, useZeroKeys, ZeroKeysPill } from "@/components/setup-cta";
|
import { useStrongKeyless, useZeroKeys, ZeroKeysPill } from "@/components/setup-cta";
|
||||||
import { DictationBar, joinDictation, useDictation } from "@/components/dictation";
|
import { DictationBar, joinDictation, useDictation } from "@/components/dictation";
|
||||||
import { useFieldCycle } from "@/components/field-cycle";
|
import { useFieldCycle } from "@/components/field-cycle";
|
||||||
@@ -217,43 +218,18 @@ export function Home({
|
|||||||
send consumes it). Practice mode with nothing picked
|
send consumes it). Practice mode with nothing picked
|
||||||
shows the pick HINT so the affordance is discoverable;
|
shows the pick HINT so the affordance is discoverable;
|
||||||
teaching mode with nothing picked renders nothing. */}
|
teaching mode with nothing picked renders nothing. */}
|
||||||
{(practiceProps.length > 0 || mode === "practice") && (
|
<ComposerActions
|
||||||
<div className="mb-2 flex items-center justify-start gap-1.5">
|
picker={(practiceProps.length > 0 || mode === "practice") && (
|
||||||
<button
|
<NodePickerControl
|
||||||
type="button"
|
count={practiceProps.length}
|
||||||
data-testid="practice-props-chip"
|
onOpen={() => setChainSelectOpen(true)}
|
||||||
onClick={() => setChainSelectOpen(true)}
|
onClear={() => setPracticeProps([])}
|
||||||
className={
|
/>
|
||||||
"flex cursor-pointer items-center gap-1.5 rounded-full border px-3 py-1.5 font-ui text-xs transition-colors " +
|
|
||||||
(practiceProps.length > 0
|
|
||||||
? "border-border bg-card text-foreground hover:bg-accent"
|
|
||||||
: "border-dashed border-border/80 bg-transparent text-muted-foreground hover:bg-accent hover:text-foreground")
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<DumbbellIcon aria-hidden="true" className="size-3.5 text-muted-foreground" />
|
|
||||||
{practiceProps.length > 0
|
|
||||||
? `${practiceProps.length} ${practiceProps.length === 1 ? "proposition" : "propositions"} selected`
|
|
||||||
: rendering
|
|
||||||
? "Pick propositions from the map"
|
|
||||||
: "Pick propositions"}
|
|
||||||
</button>
|
|
||||||
{practiceProps.length > 0 && (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
data-testid="practice-props-clear"
|
|
||||||
onClick={() => setPracticeProps([])}
|
|
||||||
className="cursor-pointer rounded-full px-2 py-1 font-ui text-xs text-muted-foreground transition-colors hover:bg-accent hover:text-foreground"
|
|
||||||
>
|
|
||||||
Clear
|
|
||||||
</button>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
setup={zeroKeys ? (
|
||||||
)}
|
<ZeroKeysPill inline onOpen={() => setSetupOpen(true)} />
|
||||||
{zeroKeys ? (
|
|
||||||
<ZeroKeysPill onOpen={() => setSetupOpen(true)} />
|
|
||||||
) : (
|
) : (
|
||||||
!configured && (
|
!configured && (
|
||||||
<div className="mb-2 flex justify-center">
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
data-testid="setup-cta"
|
data-testid="setup-cta"
|
||||||
@@ -262,9 +238,9 @@ export function Home({
|
|||||||
>
|
>
|
||||||
Set up your models
|
Set up your models
|
||||||
</button>
|
</button>
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
|
/>
|
||||||
<form
|
<form
|
||||||
className="flex items-end gap-1.5 rounded-2xl border bg-card p-1.5 shadow-lg"
|
className="flex items-end gap-1.5 rounded-2xl border bg-card p-1.5 shadow-lg"
|
||||||
onSubmit={(e) => {
|
onSubmit={(e) => {
|
||||||
|
|||||||
+10
-40
@@ -11,7 +11,7 @@ import { SetupDialog } from "@/components/setup-dialog";
|
|||||||
import { useStrongKeyless, useZeroKeys, ZeroKeysPill } from "@/components/setup-cta";
|
import { useStrongKeyless, useZeroKeys, ZeroKeysPill } from "@/components/setup-cta";
|
||||||
import { StatsBar } from "@/components/stats-bar";
|
import { StatsBar } from "@/components/stats-bar";
|
||||||
import { ChainTableOverlay } from "@/components/chain-table.tsx";
|
import { ChainTableOverlay } from "@/components/chain-table.tsx";
|
||||||
import { DumbbellIcon } from "lucide-react";
|
import { ComposerActions, NodePickerControl } from "@/components/composer-actions";
|
||||||
import type { ChatSettings, GraphSnapshot } from "@/src/core";
|
import type { ChatSettings, GraphSnapshot } from "@/src/core";
|
||||||
|
|
||||||
/** The draft's pick set (owner round 10): the SAME shape the home pick
|
/** The draft's pick set (owner round 10): the SAME shape the home pick
|
||||||
@@ -97,8 +97,8 @@ export function DraftShell({
|
|||||||
* NewSession, which owns the configured state and the SetupDialog. */
|
* NewSession, which owns the configured state and the SetupDialog. */
|
||||||
setupCta?: ReactNode;
|
setupCta?: ReactNode;
|
||||||
/** THE PICK BAR (owner round 10): the SAME pill the home screen
|
/** THE PICK BAR (owner round 10): the SAME pill the home screen
|
||||||
* carries above its composer — dashed "Pick propositions from the
|
* carries above its composer — dashed "Pick nodes" when empty,
|
||||||
* map" when empty, count + Clear when picked. The draft screen has
|
* count + Clear when picked. The draft screen has
|
||||||
* no graph behind it, so the picker opens the standalone chain-table
|
* no graph behind it, so the picker opens the standalone chain-table
|
||||||
* overlay over fetched snapshot data (owned by NewSession). */
|
* overlay over fetched snapshot data (owned by NewSession). */
|
||||||
picks?: DraftPick[];
|
picks?: DraftPick[];
|
||||||
@@ -175,7 +175,7 @@ export function DraftShell({
|
|||||||
title={mode === "practice" ? "New practice session" : "New session"}
|
title={mode === "practice" ? "New practice session" : "New session"}
|
||||||
description={
|
description={
|
||||||
mode === "practice"
|
mode === "practice"
|
||||||
? "Describe what to practice — propositions, a chain, or a problem you brought."
|
? "Pick nodes from your knowledge graph, describe what you want to practice, or bring your own problem."
|
||||||
: "The teacher will open by probing what you already know."
|
: "The teacher will open by probing what you already know."
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@@ -184,40 +184,12 @@ export function DraftShell({
|
|||||||
<div className="h-24 bg-gradient-to-t from-background via-background/85 to-transparent" />
|
<div className="h-24 bg-gradient-to-t from-background via-background/85 to-transparent" />
|
||||||
<div className="bg-background pb-4">
|
<div className="bg-background pb-4">
|
||||||
<div className={DOCK}>
|
<div className={DOCK}>
|
||||||
{setupCta}
|
<ComposerActions
|
||||||
{(picks.length > 0 || mode === "practice") && (
|
picker={(picks.length > 0 || mode === "practice") && (
|
||||||
/* pointer-events-auto: the sticky wrapper is a none-mask
|
<NodePickerControl count={picks.length} onOpen={onOpenPicker} onClear={onClearPicks} />
|
||||||
* (953b22b) — every interactive row below it must re-enable
|
|
||||||
* itself, like the composer box and the CTA row do. */
|
|
||||||
<div className="pointer-events-auto mb-2 flex items-center justify-start gap-1.5">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
data-testid="practice-props-chip"
|
|
||||||
onClick={onOpenPicker}
|
|
||||||
className={
|
|
||||||
"flex cursor-pointer items-center gap-1.5 rounded-full border px-3 py-1.5 font-ui text-xs transition-colors " +
|
|
||||||
(picks.length > 0
|
|
||||||
? "border-border bg-card text-foreground hover:bg-accent"
|
|
||||||
: "border-dashed border-border/80 bg-transparent text-muted-foreground hover:bg-accent hover:text-foreground")
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<DumbbellIcon aria-hidden="true" className="size-3.5 text-muted-foreground" />
|
|
||||||
{picks.length > 0
|
|
||||||
? `${picks.length} ${picks.length === 1 ? "proposition" : "propositions"} selected`
|
|
||||||
: "Pick propositions from the map"}
|
|
||||||
</button>
|
|
||||||
{picks.length > 0 && (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
data-testid="practice-props-clear"
|
|
||||||
onClick={onClearPicks}
|
|
||||||
className="cursor-pointer rounded-full px-2 py-1 font-ui text-xs text-muted-foreground transition-colors hover:bg-accent hover:text-foreground"
|
|
||||||
>
|
|
||||||
Clear
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
|
setup={setupCta}
|
||||||
|
/>
|
||||||
<div className="pointer-events-auto overflow-hidden rounded-2xl border bg-card shadow-lg">
|
<div className="pointer-events-auto overflow-hidden rounded-2xl border bg-card shadow-lg">
|
||||||
<Composer
|
<Composer
|
||||||
busy={busy}
|
busy={busy}
|
||||||
@@ -376,10 +348,9 @@ export function NewSession({ initialConfigured, mode = "teach" }: { initialConfi
|
|||||||
}
|
}
|
||||||
setupCta={
|
setupCta={
|
||||||
zeroKeys ? (
|
zeroKeys ? (
|
||||||
<ZeroKeysPill onOpen={() => setSetupOpen(true)} />
|
<ZeroKeysPill inline onOpen={() => setSetupOpen(true)} />
|
||||||
) : (
|
) : (
|
||||||
!configured && (
|
!configured && (
|
||||||
<div className="pointer-events-auto mb-2 flex justify-center">
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
data-testid="setup-cta"
|
data-testid="setup-cta"
|
||||||
@@ -388,7 +359,6 @@ export function NewSession({ initialConfigured, mode = "teach" }: { initialConfi
|
|||||||
>
|
>
|
||||||
Set up your models
|
Set up your models
|
||||||
</button>
|
</button>
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,9 +61,8 @@ export function useZeroKeys(): boolean {
|
|||||||
/** The pill itself — the models CTA's chrome and placement, the keys
|
/** The pill itself — the models CTA's chrome and placement, the keys
|
||||||
* chain's copy. testid "setup-cta-keys" (the models CTA keeps
|
* chain's copy. testid "setup-cta-keys" (the models CTA keeps
|
||||||
* "setup-cta"; probes can tell them apart). */
|
* "setup-cta"; probes can tell them apart). */
|
||||||
export function ZeroKeysPill({ onOpen }: { onOpen: () => void }) {
|
export function ZeroKeysPill({ onOpen, inline = false }: { onOpen: () => void; inline?: boolean }) {
|
||||||
return (
|
const button = (
|
||||||
<div className="pointer-events-auto mb-2 flex justify-center">
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
data-testid="setup-cta-keys"
|
data-testid="setup-cta-keys"
|
||||||
@@ -72,8 +71,8 @@ export function ZeroKeysPill({ onOpen }: { onOpen: () => void }) {
|
|||||||
>
|
>
|
||||||
Add a provider key
|
Add a provider key
|
||||||
</button>
|
</button>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
|
return inline ? button : <div className="pointer-events-auto mb-2 flex justify-center">{button}</div>;
|
||||||
}
|
}
|
||||||
/** Pure: the strong slot is pinned to a provider whose key is GONE (owner
|
/** Pure: the strong slot is pinned to a provider whose key is GONE (owner
|
||||||
* interlude, 2026-09-25 — the original deleted-key scenario). The catalog
|
* interlude, 2026-09-25 — the original deleted-key scenario). The catalog
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
import React from "react";
|
||||||
|
(globalThis as Record<string, unknown>).React = React;
|
||||||
|
|
||||||
|
import assert from "node:assert/strict";
|
||||||
|
import { describe, it } from "node:test";
|
||||||
|
import { renderToStaticMarkup } from "react-dom/server";
|
||||||
|
import { parseHTML } from "linkedom";
|
||||||
|
import { ComposerActions, NodePickerControl } from "../components/composer-actions.tsx";
|
||||||
|
import { ZeroKeysPill } from "../components/setup-cta.tsx";
|
||||||
|
import { DraftShell } from "../components/new-session.tsx";
|
||||||
|
|
||||||
|
const modelSetup = <button data-testid="setup-cta">Set up your models</button>;
|
||||||
|
const keySetup = <ZeroKeysPill inline onOpen={() => {}} />;
|
||||||
|
function documentFor(node: React.ReactNode) {
|
||||||
|
return parseHTML(`<html><body>${renderToStaticMarkup(node)}</body></html>`).document;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("composer action row", () => {
|
||||||
|
it("renders nothing when neither action exists", () => {
|
||||||
|
assert.equal(renderToStaticMarkup(<ComposerActions picker={false} setup={false} />), "");
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const [label, setup] of [["provider key", keySetup], ["models", modelSetup]] as const) {
|
||||||
|
it(`${label}: setup-only stays centered`, () => {
|
||||||
|
const document = documentFor(<ComposerActions setup={setup} />);
|
||||||
|
const row = document.querySelector('[data-testid="composer-actions"]')!;
|
||||||
|
assert.ok(row.classList.contains("justify-center"));
|
||||||
|
assert.equal(row.children.length, 1);
|
||||||
|
assert.equal(row.firstElementChild!.tagName, "BUTTON", "setup must not reserve a nested full-width row");
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`${label}: picker leads in one left-aligned wrapping row`, () => {
|
||||||
|
const document = documentFor(<ComposerActions picker={<NodePickerControl count={0} />} setup={setup} />);
|
||||||
|
const row = document.querySelector('[data-testid="composer-actions"]')!;
|
||||||
|
const chip = document.querySelector('[data-testid="practice-props-chip"]')!;
|
||||||
|
assert.ok(row.classList.contains("justify-start"));
|
||||||
|
assert.ok(row.classList.contains("flex-wrap"));
|
||||||
|
assert.ok(row.classList.contains("pointer-events-auto"));
|
||||||
|
assert.equal(row.children.length, 2);
|
||||||
|
assert.equal(chip.parentElement, row.firstElementChild);
|
||||||
|
assert.equal(row.lastElementChild!.tagName, "BUTTON");
|
||||||
|
assert.equal(chip.textContent, "Pick nodes");
|
||||||
|
assert.equal(document.querySelector('[data-testid="practice-props-clear"]'), null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`${label}: the practice draft uses that same row and order`, () => {
|
||||||
|
const document = documentFor(<DraftShell busy={false} text="" onChange={() => {}} onSend={() => {}} mode="practice" setupCta={setup} />);
|
||||||
|
const rows = document.querySelectorAll('[data-testid="composer-actions"]');
|
||||||
|
assert.equal(rows.length, 1);
|
||||||
|
assert.ok(rows[0]!.classList.contains("justify-start"));
|
||||||
|
assert.ok(rows[0]!.firstElementChild!.querySelector('[data-testid="practice-props-chip"]'));
|
||||||
|
assert.equal(rows[0]!.lastElementChild!.tagName, "BUTTON");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
it("picker-only remains left-aligned", () => {
|
||||||
|
const document = documentFor(<ComposerActions picker={<NodePickerControl count={0} />} />);
|
||||||
|
assert.ok(document.querySelector('[data-testid="composer-actions"]')!.classList.contains("justify-start"));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("singular and plural node counts keep Clear in the same wrapping group", () => {
|
||||||
|
for (const count of [1, 2]) {
|
||||||
|
const document = documentFor(<ComposerActions picker={<NodePickerControl count={count} />} setup={modelSetup} />);
|
||||||
|
const chip = document.querySelector('[data-testid="practice-props-chip"]')!;
|
||||||
|
const clear = document.querySelector('[data-testid="practice-props-clear"]')!;
|
||||||
|
assert.equal(chip.textContent, `${count} ${count === 1 ? "node" : "nodes"} selected`);
|
||||||
|
assert.equal(chip.parentElement, clear.parentElement);
|
||||||
|
assert.equal(chip.parentElement!.parentElement!.getAttribute("data-testid"), "composer-actions");
|
||||||
|
assert.ok(!chip.parentElement!.classList.contains("flex-wrap"), "count and Clear never wrap apart");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("existing chat callers retain the standalone provider-key pill wrapper", () => {
|
||||||
|
const document = documentFor(<ZeroKeysPill onOpen={() => {}} />);
|
||||||
|
const wrapper = document.body.firstElementChild!;
|
||||||
|
assert.equal(wrapper.tagName, "DIV");
|
||||||
|
assert.ok(wrapper.classList.contains("justify-center"));
|
||||||
|
assert.ok(wrapper.classList.contains("mb-2"));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("the practice shell preserves a selected node in teaching mode", () => {
|
||||||
|
const document = documentFor(<DraftShell busy={false} text="" onChange={() => {}} onSend={() => {}} mode="teach" picks={[{id:"n_1",name:"Parity",statement:"Odd + odd = even."}]} />);
|
||||||
|
assert.equal(document.querySelector('[data-testid="practice-props-chip"]')!.textContent, "1 node selected");
|
||||||
|
assert.ok(document.querySelector('[data-testid="practice-props-clear"]'));
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -122,7 +122,7 @@ describe("the practice draft (?mode=practice, 2026-10-04)", () => {
|
|||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
assert.match(html, /New practice session/);
|
assert.match(html, /New practice session/);
|
||||||
assert.match(html, /Describe what to practice/);
|
assert.ok(html.includes("Pick nodes from your knowledge graph, describe what you want to practice, or bring your own problem."));
|
||||||
assert.match(html, /What do you want to practice\?/);
|
assert.match(html, /What do you want to practice\?/);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -136,7 +136,7 @@ describe("the practice draft (?mode=practice, 2026-10-04)", () => {
|
|||||||
mode="practice"
|
mode="practice"
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
assert.match(html, /Pick propositions from the map/);
|
assert.match(html, />Pick nodes<\/button>/);
|
||||||
// picked: count + Clear, the home register verbatim
|
// picked: count + Clear, the home register verbatim
|
||||||
const picked = renderToStaticMarkup(
|
const picked = renderToStaticMarkup(
|
||||||
<DraftShell
|
<DraftShell
|
||||||
@@ -148,13 +148,13 @@ describe("the practice draft (?mode=practice, 2026-10-04)", () => {
|
|||||||
picks={[{ id: "n_1", name: "Parity", statement: "Odd + odd = even." }]}
|
picks={[{ id: "n_1", name: "Parity", statement: "Odd + odd = even." }]}
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
assert.match(picked, /1 proposition selected/);
|
assert.match(picked, /1 node selected/);
|
||||||
assert.match(picked, /Clear/);
|
assert.match(picked, /Clear/);
|
||||||
// teach mode with nothing picked renders nothing (home's law)
|
// teach mode with nothing picked renders nothing (home's law)
|
||||||
const teach = renderToStaticMarkup(
|
const teach = renderToStaticMarkup(
|
||||||
<DraftShell busy={false} text="" onChange={() => {}} onSend={() => {}} mode="teach" />,
|
<DraftShell busy={false} text="" onChange={() => {}} onSend={() => {}} mode="teach" />,
|
||||||
);
|
);
|
||||||
assert.ok(!teach.includes("Pick propositions from the map"));
|
assert.ok(!teach.includes('data-testid="practice-props-chip"'));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("startDraftSession carries the picks as graphContext (the home practice contract)", async () => {
|
it("startDraftSession carries the picks as graphContext (the home practice contract)", async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user