Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { createContext, useContext } from 'react';

type AutoFocusInfo = {
id: string;
type: 'key' | 'value';
type: 'key' | 'value' | 'type';
} | null;

export const AutoFocusContext = createContext<AutoFocusInfo>(null);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,34 @@
import React from 'react';
import React, { useState } from 'react';
import { expect } from 'chai';
import { render, cleanup, screen, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import HadronDocument from 'hadron-document';
import Document from './document';

const EditableDoc = ({ doc }) => {
const [editing, setEditing] = useState(false);

return (
<Document
value={doc}
editable
editing={editing}
onEditStart={() => {
setEditing(true);
}}
></Document>
);
};

describe('Document', function () {
let doc: HadronDocument;
beforeEach(function () {
doc = new HadronDocument({ str: 'abc', num: 123, date: new Date(0) });
doc = new HadronDocument({
str: 'abc',
num: 123,
date: new Date(0),
null_value: null,
});
});

afterEach(cleanup);
Expand Down Expand Up @@ -132,4 +152,72 @@ describe('Document', function () {
render(<Document value={doc}></Document>);
expect(screen.getAllByTitle('Encrypted with CSFLE')).to.have.lengthOf(2);
});

it('should allow editing the null value (COMPASS-5697)', function () {
render(<Document value={doc} editable editing></Document>);

const el = document.querySelector<HTMLElement>(
`[data-id="${doc.get('null_value').uuid}"]`
);

const typeEditor = within(el).getByTestId('hadron-document-type-editor');

userEvent.selectOptions(typeEditor, 'String');
userEvent.tab();

const valueEditor = within(el).getByTestId('hadron-document-value-editor');

userEvent.clear(valueEditor);
userEvent.keyboard('foo bar');
userEvent.tab();

expect(doc.get('null_value').currentValue.valueOf()).to.eq('foo bar');
expect(doc.get('null_value').currentType).to.eq('String');
});

it('should autofocus key editor when double-clicking key', function () {
render(<EditableDoc doc={doc}></EditableDoc>);

const el = document.querySelector<HTMLElement>(
`[data-id="${doc.get('str').uuid}"]`
);

userEvent.dblClick(within(el).getByTestId('hadron-document-clickable-key'));

const editor = within(el).getByTestId('hadron-document-key-editor');

expect(editor).to.eq(document.activeElement);
});

it('should autofocus value editor when double-clicking value', function () {
render(<EditableDoc doc={doc}></EditableDoc>);

const el = document.querySelector<HTMLElement>(
`[data-id="${doc.get('str').uuid}"]`
);

userEvent.dblClick(
within(el).getByTestId('hadron-document-clickable-value')
);

const editor = within(el).getByTestId('hadron-document-value-editor');

expect(editor).to.eq(document.activeElement);
});

it('should autofocus type editor when double-clicking a non-editable value', function () {
render(<EditableDoc doc={doc}></EditableDoc>);

const el = document.querySelector<HTMLElement>(
`[data-id="${doc.get('null_value').uuid}"]`
);

userEvent.dblClick(
within(el).getByTestId('hadron-document-clickable-value')
);

const editor = within(el).getByTestId('hadron-document-type-editor');

expect(editor).to.eq(document.activeElement);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ const HadronDocument: React.FunctionComponent<{
}, [elements, visibleFieldsCount]);
const [autoFocus, setAutoFocus] = useState<{
id: string;
type: 'key' | 'value';
type: 'key' | 'value' | 'type';
} | null>(null);

useEffect(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ export const KeyEditor: React.FunctionComponent<{
onChange={(evt) => {
onChange(evt.currentTarget.value);
}}
// See ./element.tsx
// eslint-disable-next-line jsx-a11y/no-autofocus
autoFocus={autoFocus}
className={cx(
Expand Down Expand Up @@ -236,6 +237,7 @@ export const ValueEditor: React.FunctionComponent<{
}}
onFocus={onFocus}
onBlur={onBlur}
// See ./element.tsx
// eslint-disable-next-line jsx-a11y/no-autofocus
autoFocus={autoFocus}
className={cx(
Expand All @@ -259,6 +261,7 @@ export const ValueEditor: React.FunctionComponent<{
}}
onFocus={onFocus}
onBlur={onBlur}
// See ./element.tsx
// eslint-disable-next-line jsx-a11y/no-autofocus
autoFocus={autoFocus}
className={cx(
Expand Down Expand Up @@ -319,10 +322,11 @@ const typeEditorActive = css({

export const TypeEditor: React.FunctionComponent<{
editing?: boolean;
autoFocus?: boolean;
type: HadronElementType['type'];
onChange(newVal: HadronElementType['type']): void;
visuallyActive?: boolean;
}> = ({ editing, type, onChange, visuallyActive }) => {
}> = ({ editing, autoFocus, type, onChange, visuallyActive }) => {
return (
<>
{editing && (
Expand All @@ -332,6 +336,9 @@ export const TypeEditor: React.FunctionComponent<{
<select
value={type}
data-testid="hadron-document-type-editor"
// See ./element.tsx
// eslint-disable-next-line jsx-a11y/no-autofocus
autoFocus={autoFocus}
onChange={(evt) => {
onChange(evt.currentTarget.value as HadronElementType['type']);
}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,21 +40,15 @@ function getEditorByType(type: HadronElementType['type']) {
function useElementEditor(el: HadronElementType) {
const editor = useRef<EditorType | null>(null);

if (!editor.current) {
if (
!editor.current ||
editor.current?.element !== el ||
editor.current?.type !== el.currentType
) {
const Editor = getEditorByType(el.currentType);
editor.current = new Editor(el);
}

useEffect(() => {
if (
editor.current?.element.uuid !== el.uuid ||
editor.current?.element.currentType !== el.currentType
) {
const Editor = getEditorByType(el.currentType);
editor.current = new Editor(el);
}
}, [el, el.uuid, el.currentType]);

return editor.current;
}

Expand Down Expand Up @@ -291,7 +285,7 @@ export const HadronElement: React.FunctionComponent<{
value: HadronElementType;
editable: boolean;
editingEnabled: boolean;
onEditStart?: (id: string, field: 'key' | 'value') => void;
onEditStart?: (id: string, field: 'key' | 'value' | 'type') => void;
allExpanded: boolean;
lineNumberSize: number;
onAddElement(el: HadronElementType): void;
Expand Down Expand Up @@ -507,10 +501,23 @@ export const HadronElement: React.FunctionComponent<{
}}
></ValueEditor>
) : (
<BSONValue
type={type.value as any}
value={value.originalValue}
></BSONValue>
<div
data-testid={
editable && !editingEnabled
? 'hadron-document-clickable-value'
: undefined
}
onDoubleClick={() => {
if (editable && !editingEnabled) {
onEditStart?.(element.uuid, 'type');
}
}}
>
<BSONValue
type={type.value as any}
value={value.originalValue}
></BSONValue>
</div>
)}
</div>
{editable && (
Expand All @@ -521,6 +528,9 @@ export const HadronElement: React.FunctionComponent<{
<TypeEditor
editing={editingEnabled}
type={type.value}
// See above
// eslint-disable-next-line jsx-a11y/no-autofocus
autoFocus={autoFocus?.id === id && autoFocus.type === 'type'}
onChange={(newType) => {
type.change(newType);
}}
Expand Down
1 change: 1 addition & 0 deletions packages/hadron-document/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ declare class HadronDocument extends EventEmitter {
export declare class Editor {
constructor(element: HadronElement);
element: HadronElement;
type: HadronElement['type'];
edit(value: unknown): void;
paste(value: string): void;
size(): number;
Expand Down
11 changes: 7 additions & 4 deletions packages/hadron-document/src/editor/standard.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class StandardEditor {
*/
constructor(element) {
this.element = element;
this.type = element.currentType;
this.editing = false;
}

Expand Down Expand Up @@ -50,7 +51,6 @@ class StandardEditor {
}
}


/**
* Get the number of characters the value should display.
*
Expand All @@ -63,18 +63,21 @@ class StandardEditor {
}

/**
* Get the value being edited.
* Get the value being edited. Always returns a string because this value will
* always be used by browser input elements that operate on nothing but
* strings
*
* @returns {Object} The value.
* @returns {string} The value.
*/
value() {
return this.element.currentValue;
return String(this.element.currentValue);
}

// Standard editing requires no special start/complete behaviour.
start() {
this.editing = true;
}

complete() {
this.editing = false;
}
Expand Down