Skip to content

Commit

Permalink
Add mapping for interstitial searchText nodes, all tests now pass
Browse files Browse the repository at this point in the history
  • Loading branch information
jonathonherbert committed Sep 24, 2024
1 parent acb9950 commit d586836
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 38 deletions.
34 changes: 22 additions & 12 deletions prosemirror-client/src/cqlInput/CqlInput.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,16 @@ import { findByTestId, findByText } from "@testing-library/dom";
import { CqlClientService } from "../services/CqlService";
import { TestTypeaheadHelpers } from "../lang/typeaheadHelpersTest";
import { createEditor, ProsemirrorTestChain } from "jest-prosemirror";
import { doc } from "./schema";
import { createCqlPlugin } from "./plugin";
import { redo, undo } from "prosemirror-history";
import { bottomOfLine, topOfLine } from "./commands";
import { keymap } from "prosemirror-keymap";
import { logNode, mapResult, tokensToDoc } from "./utils";

const typeheadHelpers = new TestTypeaheadHelpers();
const testCqlService = new CqlClientService(typeheadHelpers.fieldResolvers);

const createCqlEditor = () => {
const createCqlEditor = async (initialQuery: string = "") => {
const container = document.createElement("div");
const typeaheadEl = document.createElement("div");
typeaheadEl.setAttribute("data-testid", typeaheadTestId);
Expand All @@ -35,7 +35,15 @@ const createCqlEditor = () => {
onChange: ({ cqlQuery }) => dispatch(cqlQuery),
});

const editor = createEditor(doc.create(), {
const queryToProseMirrorTokens = async (query: string) => {
const result = await testCqlService.fetchResult(query);
const { tokens } = mapResult(result);
return tokensToDoc(tokens);
};

const doc = await queryToProseMirrorTokens(initialQuery)

const editor = createEditor(doc, {
plugins: [
plugin,
keymap({
Expand All @@ -47,6 +55,8 @@ const createCqlEditor = () => {
],
});

editor.selectText('end')

container.appendChild(editor.view.dom);

const subscribers: Array<(s: string) => void> = [];
Expand Down Expand Up @@ -103,7 +113,7 @@ describe("CqlInput", () => {

describe("input", () => {
it("accepts and displays a basic query", async () => {
const { editor, waitFor } = createCqlEditor();
const { editor, waitFor } = await createCqlEditor();

await editor.insertText("example");

Expand All @@ -113,7 +123,7 @@ describe("CqlInput", () => {

describe("typeahead", () => {
it("displays a popover for chip keys at the start of a query", async () => {
const { editor, container } = createCqlEditor();
const { editor, container } = await createCqlEditor();

await editor.insertText("example +");

Expand All @@ -124,7 +134,7 @@ describe("CqlInput", () => {
});

it("displays a popover for chip keys after search text", async () => {
const { editor, container } = createCqlEditor();
const { editor, container } = await createCqlEditor();

await editor.insertText("+");

Expand All @@ -135,9 +145,9 @@ describe("CqlInput", () => {
});

it("displays a popover for chip keys after another chip", async () => {
const { editor, container } = createCqlEditor();
const { editor, container } = await createCqlEditor("+tag:a");

await editor.insertText("+tag:a +");
editor.insertText(" +");

const popoverContainer = await findByTestId(container, typeaheadTestId);

Expand All @@ -146,7 +156,7 @@ describe("CqlInput", () => {
});

it("accepts the given value when a popover appears", async () => {
const { editor, container, waitFor } = createCqlEditor();
const { editor, container, waitFor } = await createCqlEditor();
await editor.insertText("example +");

await waitFor("example +");
Expand All @@ -159,15 +169,15 @@ describe("CqlInput", () => {

describe("caret movement and selection", () => {
it("ctrl-a moves the caret to the beginning of the input", async () => {
const { editor, waitFor } = createCqlEditor();
const { editor, waitFor } = await createCqlEditor();

await editor.insertText("a").shortcut("Ctrl-a").insertText("b");

await waitFor("ba");
});

it("ctrl-e moves the caret to the end of the input", async () => {
const { editor, waitFor } = createCqlEditor();
const { editor, waitFor } = await createCqlEditor();

await editor
.insertText("a")
Expand All @@ -179,7 +189,7 @@ describe("CqlInput", () => {
});

it("permits content before query fields", async () => {
const { editor, waitFor } = createCqlEditor();
const { editor, waitFor } = await createCqlEditor();

await editor.insertText("+tag").shortcut("Ctrl-a").insertText("a ");

Expand Down
2 changes: 0 additions & 2 deletions prosemirror-client/src/cqlInput/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -292,8 +292,6 @@ export const createCqlPlugin = ({
const { tokens, suggestions, ast, error, queryResult } =
mapResult(result);

console.log(tokens, suggestions, ast, error, queryResult);

const newDoc = tokensToDoc(tokens);

if (debugASTContainer) {
Expand Down
1 change: 0 additions & 1 deletion prosemirror-client/src/cqlInput/utils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ describe("utils", () => {
const tokens = await queryToProseMirrorTokens(query);
const mappedTokens = mapTokens(tokens);
const node = tokensToDoc(tokens);
logNode(node);
return mappedTokens.map(({ from, to, tokenType }) => {
return tokenType !== "EOF" ? node.textBetween(from, to) : "";
});
Expand Down
70 changes: 47 additions & 23 deletions prosemirror-client/src/cqlInput/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ const tokensToPreserve = ["QUERY_FIELD_KEY", "QUERY_VALUE", "EOF"];

const joinSearchTextTokens = (tokens: ProseMirrorToken[]) =>
tokens.reduce((acc, token) => {
console.log(token.tokenType);
if (tokensToPreserve.includes(token.tokenType)) {
return acc.concat(token);
}
Expand All @@ -44,6 +43,30 @@ const joinSearchTextTokens = (tokens: ProseMirrorToken[]) =>
});
}, [] as ProseMirrorToken[]);

const getQueryFieldKeyRange = (from: number): [number, number, number] =>
// chip begin (+1)
// chipKey begin (+1)
// leading char ('+') (+1)
[from - 1, 0, 3];

const getQueryValueRanges = (
from: number,
to: number
): [number, number, number][] => [
// leading char (':')
[from - 1, 0, 1],
// chipValue end (+1)
[to, 0, 1],
];

const getSearchTextRanges = (
from: number,
to: number
): [number, number, number][] => [
[from, 0, 1], // searchText begin (+1)
[to, 0, 0], // searchText end (+1)
];

/**
* Create a mapping from ProseMirrorToken positions to document positions.
*
Expand All @@ -61,9 +84,14 @@ const joinSearchTextTokens = (tokens: ProseMirrorToken[]) =>
*
* is represented in ProseMirror as
*
* <doc> <searchText> s t r </searchText> <chipWrapper> <chip> <chipKey> k </chipKey> <chipValue> v </chipValue> </chip> </chipWrapper> </doc>
* <doc> <searchText> s t r </searchText> <chipWrapper> <chip> <chipKey> k
* </chipKey> <chipValue> v </chipValue> </chip> </chipWrapper> </doc>
* | | | | | | | | | | | | | | | | | |
* 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
* 0 1 2 3 4 5 6 7 8 9 10 11
* 12 13 14 15 16 17
*
* NB: This function will not fill out the searchText at the beginning or end of
* the document, relying on ProseMirror's schema to autofill missing nodes.
*/
export const createProseMirrorTokenToDocumentMap = (
tokens: ProseMirrorToken[]
Expand All @@ -74,29 +102,24 @@ export const createProseMirrorTokenToDocumentMap = (
const compactedTokenRanges = joinSearchTextTokens(tokens);

const ranges = compactedTokenRanges.reduce<[number, number, number][]>(
(ranges, { tokenType, from, to }) => {
console.log({tokenType, from, to})
(accRanges, { tokenType, from, to }, index, tokens) => {
switch (tokenType) {
case "QUERY_FIELD_KEY":
// We add mappings here to accommodate the positions occupied by node boundaries.
return ranges.concat(
// chip begin (+1)
// chipKey begin (+1)
// leading char ('+')
[from - 1, 0, 3]
// If this field is preceded by a field value, we must add a
// searchText mapping as well – the editor will add searchText nodes
// between consecutive chips, which we must account for.
const previousToken = tokens[index - 1];
const isPrecededByChip = previousToken?.tokenType === "QUERY_VALUE";
return accRanges.concat(
...(isPrecededByChip
? getSearchTextRanges(previousToken?.to, from - 1)
: []),
getQueryFieldKeyRange(from)
);
case "QUERY_VALUE":
return ranges.concat(
// leading char (':')
[from - 1, 0, 1],
// chipValue end (+1)
[to, 0, 1]
);
return accRanges.concat(...getQueryValueRanges(from, to));
default:
return ranges.concat(
[from, 0, 1], // searchText begin (+1)
[to, 0, 0] // searchText end (+1)
);
return accRanges.concat(...getSearchTextRanges(from, to));
}
},
[]
Expand Down Expand Up @@ -128,8 +151,10 @@ export const tokensToDoc = (_tokens: ProseMirrorToken[]): Node => {
case "QUERY_FIELD_KEY":
const tokenKey = token.literal;
const tokenValue = tokens[index + 1]?.literal;

const previousToken = tokens[index - 1];
const isPrecededByChip = previousToken?.tokenType === "QUERY_VALUE";
return acc.concat(
...(isPrecededByChip ? [searchText.create()] : []),
chipWrapper.create(undefined, [
chip.create(undefined, [
chipKey.create(
Expand All @@ -155,7 +180,6 @@ export const tokensToDoc = (_tokens: ProseMirrorToken[]): Node => {
[] as Node[]
);


return doc.create(undefined, nodes);
};

Expand Down

0 comments on commit d586836

Please sign in to comment.