fix(editor): Disable expression editor modal opening on readonly field (#8457)

This commit is contained in:
Csaba Tuncsik 2024-01-29 16:34:10 +01:00 committed by GitHub
parent 9e93980957
commit eb27ed068b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 118 additions and 20 deletions

View file

@ -1,5 +1,5 @@
<template>
<div ref="root" @keydown.stop></div>
<div ref="root" :class="$style.editor" @keydown.stop></div>
</template>
<script lang="ts">
@ -67,6 +67,7 @@ export default defineComponent({
history(),
expressionInputHandler(),
EditorView.lineWrapping,
EditorView.editable.of(!this.isReadOnly),
EditorState.readOnly.of(this.isReadOnly),
EditorView.contentAttributes.of({ 'data-gramm': 'false' }), // disable grammarly
EditorView.domEventHandlers({ scroll: forceParse }),
@ -146,4 +147,14 @@ export default defineComponent({
});
</script>
<style lang="scss"></style>
<style lang="scss" module>
.editor div[contenteditable='false'] {
background-color: var(--disabled-fill, var(--color-background-light));
cursor: not-allowed;
}
</style>
<style lang="scss" scoped>
:deep(.cm-content) {
border-radius: var(--border-radius-base);
}
</style>

View file

@ -75,6 +75,7 @@ export default defineComponent({
outputTheme(),
EditorState.readOnly.of(true),
EditorView.lineWrapping,
EditorView.editable.of(false),
EditorView.domEventHandlers({ scroll: forceParse }),
];

View file

@ -20,8 +20,11 @@
@blur="onBlur"
@change="onChange"
/>
<n8n-icon
<n8n-button
v-if="!isDragging"
square
outline
type="tertiary"
icon="external-link-alt"
size="xsmall"
:class="$style['expression-editor-modal-opener']"
@ -185,27 +188,21 @@ export default defineComponent({
background-color: var(--color-code-background);
padding: 3px;
line-height: 9px;
border: var(--border-base);
border-top-left-radius: var(--border-radius-base);
border-bottom-right-radius: var(--input-border-bottom-right-radius, var(--border-radius-base));
border-right-color: var(
--input-border-right-color,
var(--input-border-color, var(--border-color-base))
);
border-bottom-color: var(
--input-border-bottom-color,
var(--input-border-color, var(--border-color-base))
);
border: var(--input-border-color, var(--border-color-base))
var(--input-border-style, var(--border-style-base))
var(--input-border-width, var(--border-width-base));
cursor: pointer;
&:hover {
border: var(--input-border-color, var(--border-color-base))
var(--input-border-style, var(--border-style-base))
var(--input-border-width, var(--border-width-base));
}
svg {
width: 9px !important;
height: 9px;
transform: rotate(270deg);
&:hover {
color: var(--color-primary);
}
}
}

View file

@ -1,5 +1,5 @@
<template>
<div ref="root" data-test-id="inline-expression-editor-input"></div>
<div ref="root" :class="$style.editor" data-test-id="inline-expression-editor-input"></div>
</template>
<script lang="ts">
@ -169,4 +169,18 @@ export default defineComponent({
});
</script>
<style lang="scss"></style>
<style lang="scss" module>
.editor div[contenteditable='false'] {
background-color: var(--disabled-fill, var(--color-background-light));
cursor: not-allowed;
}
</style>
<style lang="scss" scoped>
:deep(.cm-editor) {
padding-left: 0;
}
:deep(.cm-content) {
padding-left: var(--spacing-2xs);
}
</style>

View file

@ -0,0 +1,39 @@
import userEvent from '@testing-library/user-event';
import { createComponentRenderer } from '@/__tests__/render';
import ExpressionEditorModalInput from '@/components/ExpressionEditorModal/ExpressionEditorModalInput.vue';
const renderComponent = createComponentRenderer(ExpressionEditorModalInput);
const originalRangeGetBoundingClientRect = Range.prototype.getBoundingClientRect;
const originalRangeGetClientRects = Range.prototype.getClientRects;
describe('ExpressionParameterInput', () => {
beforeAll(() => {
Range.prototype.getBoundingClientRect = vi.fn();
Range.prototype.getClientRects = () => ({
item: vi.fn(),
length: 0,
[Symbol.iterator]: vi.fn(),
});
});
afterAll(() => {
Range.prototype.getBoundingClientRect = originalRangeGetBoundingClientRect;
Range.prototype.getClientRects = originalRangeGetClientRects;
});
test.each([
['not be editable', 'readonly', true, ''],
['be editable', 'not readonly', false, 'test'],
])('should %s when %s', async (_, __, isReadOnly, expected) => {
const { getByRole } = renderComponent({
props: {
modelValue: '',
path: '',
isReadOnly,
},
});
await userEvent.type(getByRole('textbox'), 'test');
expect(getByRole('textbox')).toHaveTextContent(expected);
});
});

View file

@ -0,0 +1,36 @@
import { createPinia, setActivePinia } from 'pinia';
import userEvent from '@testing-library/user-event';
import { createComponentRenderer } from '@/__tests__/render';
import { useWorkflowsStore } from '@/stores/workflows.store';
import { useNDVStore } from '@/stores/ndv.store';
import ExpressionParameterInput from '@/components/ExpressionParameterInput.vue';
const renderComponent = createComponentRenderer(ExpressionParameterInput);
let pinia: ReturnType<typeof createPinia>;
let workflowsStore: ReturnType<typeof useWorkflowsStore>;
let ndvStore: ReturnType<typeof useNDVStore>;
describe('ExpressionParameterInput', () => {
beforeEach(() => {
pinia = createPinia();
setActivePinia(pinia);
workflowsStore = useWorkflowsStore();
ndvStore = useNDVStore();
});
test.each([
['not readonly', false, expect.anything()],
['readonly', true, expect.anything()],
])('should emit open expression editor modal when %s', async (_, isReadOnly, expected) => {
const { getByTestId, emitted } = renderComponent({
props: {
modelValue: '',
isReadOnly,
},
});
await userEvent.click(getByTestId('expander'));
expect(emitted().modalOpenerClick).toEqual(expected);
});
});