Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Resurrect TrashCan tests #587

Merged
merged 1 commit into from
Nov 16, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 0 additions & 34 deletions packages/codemirror-blocks/spec/ui/TrashCan-test-old.js

This file was deleted.

104 changes: 104 additions & 0 deletions packages/codemirror-blocks/spec/ui/TrashCan-test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { cleanup, render, screen, fireEvent } from "@testing-library/react";
import CodeMirror from "codemirror";
import React from "react";
import { useDrag } from "react-dnd";
import Context from "../../src/components/Context";
import { ItemTypes } from "../../src/dnd";
import { CodeMirrorFacade } from "../../src/editor";
import { addLanguage, Language } from "../../src/languages";
import { AppStore, createAppStore } from "../../src/state/store";
import * as actions from "../../src/state/actions";
import * as selectors from "../../src/state/selectors";
import TrashCan from "../../src/ui/TrashCan";
import { ASTNode } from "../../src/ast";
import { Literal } from "../../src/nodes";

let language: Language;
beforeAll(() => {
language = addLanguage({
id: "some-lang",
name: "some lang",
parse: (code: string) => {
return code
.split(/\s+/)
.map((s) => Literal({ line: 0, ch: 0 }, { line: 0, ch: 1 }, s));
},
});
});

let store!: AppStore;
let editor!: CodeMirrorFacade;
beforeEach(() => {
editor = new CodeMirrorFacade(CodeMirror(document.body));
store = createAppStore();
});
afterEach(() => {
cleanup();
});

const renderWithContext = (el: React.ReactElement) =>
render(<Context store={store}>{el}</Context>);

it("should render a trashcan", () => {
const result = renderWithContext(
<TrashCan language={language} editor={editor} />
);
expect(result.container).toMatchInlineSnapshot(`
<div>
<div
aria-hidden="true"
class="TrashCan"
>
🗑️
</div>
</div>
`);
});

const DragStub = ({ node }: { node: ASTNode }) => {
const [_, connectDragSource] = useDrag({
type: ItemTypes.NODE,
item: () => ({ id: node.id }),
});
return connectDragSource(<div>draggable thing</div>);
};

describe("dragging a node over the trashcan", () => {
let trashcan: HTMLElement;
let draggable: HTMLElement;
beforeEach(() => {
editor.setValue("a b c");
store.dispatch(actions.setBlockMode(true, editor.getValue(), language));

const ast = selectors.getAST(store.getState());

renderWithContext(
<div>
<DragStub node={ast.rootNodes[0]} />
<TrashCan language={language} editor={editor} />
</div>
);
trashcan = document.querySelector(".TrashCan")!;
draggable = screen.getByText("draggable thing");
fireEvent.dragStart(draggable);
fireEvent.dragEnter(trashcan);
fireEvent.dragOver(trashcan);
});

it("should add an 'over' css class", () => {
expect(trashcan).toMatchInlineSnapshot(`
<div
aria-hidden="true"
class="TrashCan over"
>
🗑️
</div>
`);
});

it("should delete the node that was dropped onto it", () => {
expect(selectors.getAST(store.getState()).toString()).toBe("a\nb\nc");
fireEvent.drop(trashcan);
expect(selectors.getAST(store.getState()).toString()).toBe("b\nc");
});
});
4 changes: 3 additions & 1 deletion packages/codemirror-blocks/src/editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,9 @@ export class CodeMirrorFacade implements CMBEditor {
return items[i];
}
}
throw new Error(`No undoable found`);
// TODO(pcardune): Make it ok to return "undefined" when there is no
// topmost action. See https://github.com/bootstrapworld/codemirror-blocks/issues/586.
return {};
}
getAllBlockNodeMarkers(): BlockNodeMarker[] {
return this.codemirror
Expand Down
7 changes: 6 additions & 1 deletion packages/codemirror-blocks/src/edits/performEdits.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,11 @@ export function applyEdits(
return err(result.exception);
}

export type PerformEditsResult = Result<{
newAST: AST;
focusId?: string | undefined;
}>;

/**
* performEdits : String, AST, Array<Edit>, Callback?, Callback? -> Void
*
Expand All @@ -209,7 +214,7 @@ export const performEdits =
parse: Language["parse"],
editor: CMBEditor,
annt?: string
): AppThunk<Result<{ newAST: AST; focusId?: string | undefined }>> =>
): AppThunk<PerformEditsResult> =>
(dispatch, getState) => {
// Perform the text edits, and update the ast.
const result = applyEdits(
Expand Down
16 changes: 16 additions & 0 deletions packages/codemirror-blocks/src/state/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
edit_replace,
edit_overwrite,
EditInterface,
PerformEditsResult,
} from "../edits/performEdits";
import { AST, ASTNode, Pos } from "../ast";
import { CMBEditor, ReadonlyCMBEditor, ReadonlyRangedText } from "../editor";
Expand Down Expand Up @@ -99,6 +100,21 @@ export const setBlockMode =
return result;
};

/**
* Deletes a single node from the AST
*/
export const deleteASTNode =
(
language: Language,
editor: CMBEditor,
srcNodeId: string
): AppThunk<PerformEditsResult> =>
(dispatch, getState) => {
const ast = selectors.getAST(getState());
const edits = [edit_delete(ast, ast.getNodeByIdOrThrow(srcNodeId))];
return dispatch(performEdits(edits, language.parse, editor));
};

/**
* Check whether the given code can be parsed into an AST and then
* pretty printed back into code again, and then back into an AST again.
Expand Down
13 changes: 7 additions & 6 deletions packages/codemirror-blocks/src/ui/TrashCan.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import React from "react";
import { ItemTypes } from "../dnd";
import { useDrop } from "react-dnd";
import { useDispatch, useSelector } from "react-redux";
import { edit_delete, performEdits } from "../edits/performEdits";
import { AppDispatch } from "../state/store";
import * as selectors from "../state/selectors";
import * as actions from "../state/actions";
import { CMBEditor } from "../editor";
import type { Language } from "../CodeMirrorBlocks";
require("./TrashCan.less");
Expand All @@ -16,16 +16,17 @@ const TrashCan = (props: { editor: CMBEditor; language: Language }) => {
() => ({
accept: ItemTypes.NODE,
drop: (item: { id: string }) => {
const srcNode = item.id ? ast.getNodeById(item.id) : null; // null if dragged from toolbar
if (!srcNode) return; // Someone dragged from the toolbar to the trash can.
const edits = [edit_delete(ast, ast.getNodeByIdOrThrow(srcNode.id))];
const srcNode = item.id ? ast.getNodeByIdOrThrow(item.id) : null; // null if dragged from toolbar
if (!srcNode) {
return; // Someone dragged from the toolbar to the trash can.
}
return dispatch(
performEdits(edits, props.language.parse, props.editor)
actions.deleteASTNode(props.language, props.editor, srcNode.id)
);
},
collect: (monitor) => ({ isOver: monitor.isOver() }),
}),
[performEdits, ast]
[ast]
);

const classNames = "TrashCan" + (isOver ? " over" : "");
Expand Down