refactor(schema): simplify validation and OpenAPI generation (#2709)

This commit is contained in:
Hampus authored and GitHub committed 2026-09-11 23:24:26 +02:00
1 parent 4bc5593f9f
commit de1fd95a99
510 files changed
+29494 -36131

No files matched your search

@@ -1,12 +1,17 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import assert from 'node:assert/strict';
import {registerComposerEnter} from '@app/features/lexical/composer/ComposerEnter';
import {$replaceComposerRange, $selectComposerRange} from '@app/features/lexical/composer/composerOffsets';
import {ComposerMentionNode} from '@app/features/lexical/composer/nodes/ComposerMentionNode';
import {ComposerPlainSegmentNode} from '@app/features/lexical/composer/nodes/ComposerPlainSegmentNode';
import {$createSlashSlotNode, SlashSlotNode} from '@app/features/lexical/composer/nodes/SlashSlotNode';
import {
$createSlashSlotNode,
$isSlashSlotNode,
SlashSlotNode,
} from '@app/features/lexical/composer/nodes/SlashSlotNode';
import {SlashSlotPlaceholderNode} from '@app/features/lexical/composer/nodes/SlashSlotPlaceholderNode';
import {$getRoot, $setSelection, createEditor, type ElementNode, KEY_ENTER_COMMAND, type LexicalEditor} from 'lexical';
import {$getRoot, $isElementNode, $setSelection, createEditor, KEY_ENTER_COMMAND, type LexicalEditor} from 'lexical';
import {describe, expect, it, vi} from 'vitest';
vi.mock('@app/features/lexical/composer/nodes/ComposerMentionPill', () => ({ComposerMentionPill: () => null}));
@@ -45,16 +50,22 @@ function typedWithRequiredSlot(text: string, offset: number): LexicalEditor {
return createComposer(() => {
$replaceComposerRange(0, 0, {kind: 'text', text}, {leading: false, trailing: false});
const slot = $createSlashSlotNode('value', 'string', true);
$getRoot().getFirstChildOrThrow<ElementNode>().append(slot);
const paragraph = $getRoot().getFirstChildOrThrow();
assert($isElementNode(paragraph), 'Expected composer paragraph');
paragraph.append(slot);
slot.ensurePlaceholder();
$selectComposerRange(offset, offset);
});
}
function slotValidity(editor: LexicalEditor): string {
return editor.read(() =>
$getRoot().getFirstChildOrThrow<ElementNode>().getLastChildOrThrow<SlashSlotNode>().getValidity(),
);
return editor.read(() => {
const paragraph = $getRoot().getFirstChildOrThrow();
assert($isElementNode(paragraph), 'Expected composer paragraph');
const slot = paragraph.getLastChildOrThrow();
assert($isSlashSlotNode(slot), 'Expected required slash command slot');
return slot.getValidity();
});
}
function pressEnter(
@@ -1,5 +1,6 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import assert from 'node:assert/strict';
import {registerComposerMarkdownHighlight} from '@app/features/lexical/composer/ComposerMarkdownHighlight';
import {$hydrateComposerFromDraft, $projectComposer} from '@app/features/lexical/composer/ComposerSerialization';
import {$getComposerSelectionRange, $selectComposerRange} from '@app/features/lexical/composer/composerOffsets';
@@ -24,7 +25,6 @@ import {
$isElementNode,
$isTextNode,
createEditor,
type ElementNode,
type LexicalEditor,
type RangeSelection,
} from 'lexical';
@@ -282,7 +282,9 @@ describe('registerComposerMarkdownHighlight with silentMessagePrefix', () => {
]);
expect(readLines(editor)).toEqual([[['@silent', SILENT_STYLE], [' ', ''], 'composer-mention']]);
const mention = editor.getEditorState().read(() => {
const pill = $getRoot().getFirstChildOrThrow<ElementNode>().getLastChild();
const paragraph = $getRoot().getFirstChildOrThrow();
assert($isElementNode(paragraph), 'Expected composer paragraph');
const pill = paragraph.getLastChild();
return $isComposerMentionNode(pill) ? {literal: pill.isLiteral(), presentation: pill.getPresentation()} : null;
});
expect(mention).toEqual({literal: false, presentation: ComposerMentionPresentation.none});
@@ -1,5 +1,6 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import assert from 'node:assert/strict';
import {
$getComposerClipboardSelection,
$insertComposerClipboardSlice,
@@ -42,6 +43,7 @@ import {
$createTextNode,
$getRoot,
$getSelection,
$isElementNode,
$isRangeSelection,
$setCompositionKey,
$setSelection,
@@ -142,7 +144,9 @@ function describeNode(node: LexicalNode): string {
}
function $paragraph(): ElementNode {
return $getRoot().getFirstChildOrThrow<ElementNode>();
const paragraph = $getRoot().getFirstChildOrThrow();
assert($isElementNode(paragraph), 'Expected composer paragraph');
return paragraph;
}
function children(editor: LexicalEditor): Array<string> {
@@ -517,9 +521,13 @@ describe('typed special mentions', () => {
'text:@everyone ',
'slash-slot:@here ',
]);
expect(editor.read(() => $paragraph().getLastChildOrThrow<ElementNode>().getChildren().map(describeNode))).toEqual([
'text:@here ',
]);
expect(
editor.read(() => {
const slot = $paragraph().getLastChildOrThrow();
assert($isElementNode(slot), 'Expected slash command slot element');
return slot.getChildren().map(describeNode);
}),
).toEqual(['text:@here ']);
});
it('restores a draft special mention and projects it back unchanged', () => {
@@ -37,7 +37,7 @@ export interface ComposerTypeaheadPluginProps {
listboxId: string;
enabled: boolean;
onSelect: (option: AutocompleteOption) => void;
activeRef: React.MutableRefObject<boolean>;
activeRef: React.RefObject<boolean>;
}
export const ComposerTypeaheadPlugin = ({
@@ -250,7 +250,7 @@ function SelectionToolbarSurface({
onFocusRequestHandled: () => void;
onDismiss: () => void;
toolbarRef: React.RefObject<HTMLElement | null>;
savedSelectionRef: React.MutableRefObject<ComposerSelectionOffsets | null>;
savedSelectionRef: React.RefObject<ComposerSelectionOffsets | null>;
}) {
const {i18n} = useLingui();
const [focusIndex, setFocusIndex] = useState(0);
@@ -33,7 +33,7 @@ export interface SlashSlotAutocompletePluginProps {
listboxId: string;
enabled: boolean;
onSelect: (option: AutocompleteOption) => void;
activeRef: React.MutableRefObject<boolean>;
activeRef: React.RefObject<boolean>;
}
export const SlashSlotAutocompletePlugin = ({
@@ -1,5 +1,6 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import assert from 'node:assert/strict';
import {registerComposerMarkdownHighlight} from '@app/features/lexical/composer/ComposerMarkdownHighlight';
import {type CodeBlockWrapPlan, planCodeBlockWrap} from '@app/features/lexical/composer/codeBlockWrap';
import {
@@ -25,8 +26,8 @@ import {createEmptyHistoryState, registerHistory} from '@lexical/history';
import {
$getNodeByKey,
$getRoot,
$isElementNode,
createEditor,
type ElementNode,
type LexicalEditor,
type NodeKey,
REDO_COMMAND,
@@ -307,13 +308,14 @@ function composerState(editor: LexicalEditor): {
}
function atomicKeys(editor: LexicalEditor): Array<NodeKey> {
return editor.read(() =>
$getRoot()
.getFirstChildOrThrow<ElementNode>()
return editor.read(() => {
const paragraph = $getRoot().getFirstChildOrThrow();
assert($isElementNode(paragraph), 'Expected composer paragraph');
return paragraph
.getChildren()
.filter((node) => $isComposerMentionNode(node) || $isComposerCustomEmojiNode(node))
.map((node) => node.getKey()),
);
.map((node) => node.getKey());
});
}
function atomicState(editor: LexicalEditor, keys: Array<NodeKey>): Array<{attached: boolean; literal: boolean} | null> {