- {
- e.preventDefault();
- const text = window.getSelection()?.toString();
- navigator.clipboard.writeText(text || '');
- }}
- onCut={
- /* istanbul ignore next :
- The editor is read-only, so the onCut function will never be called. */
- (e) => {
+
+ {
e.preventDefault();
const text = window.getSelection()?.toString();
navigator.clipboard.writeText(text || '');
- setValue([{ type: 'paragraph', children: [{ text: '' }] }]);
+ }}
+ onCut={
+ /* istanbul ignore next :
+ The editor is read-only, so the onCut function will never be called. */
+ (e) => {
+ e.preventDefault();
+ const text = window.getSelection()?.toString();
+ navigator.clipboard.writeText(text || '');
+ setValue([{ type: 'paragraph', children: [{ text: '' }] }]);
+ }
}
- }
- readOnly={true}
- decorate={([node, path]) => {
- if (!Text.isText(node)) return [];
- const stringPath = path.join(',');
- /* istanbul ignore next: allPathDecorationsMap[stringPath] cannot be null */
- return allPathDecorationsMap[stringPath] || [];
- }}
- renderLeaf={(props: any) => {
- const { leaf, children, attributes } = props;
- const textStyles: undefined | string = (() => {
- if (
- [
- 'objectPropertyStartQuotes',
- 'objectPropertyEndQuotes',
- ].includes(leaf.syntaxPart?.type)
- )
- return 'text-blue-200';
- if (['objectProperty'].includes(leaf.syntaxPart?.type)) {
- const isJsonScope = jsonPathsWithJsonScope
- .filter(
- (jsonPathWithScope) =>
- jsonPathWithScope.scope ===
- JsonSchemaScope.TypeDefinition,
- )
- .map(
- (jsonPathsWithJsonScope) => jsonPathsWithJsonScope.jsonPath,
- )
- .includes(leaf.syntaxPart?.parentJsonPath);
+ readOnly={true}
+ decorate={([node, path]) => {
+ if (!Text.isText(node)) return [];
+ const stringPath = path.join(',');
+ /* istanbul ignore next: allPathDecorationsMap[stringPath] cannot be null */
+ return allPathDecorationsMap[stringPath] || [];
+ }}
+ renderLeaf={(props: any) => {
+ const { leaf, children, attributes } = props;
+ const textStyles: undefined | string = (() => {
if (
- isJsonScope &&
- jsonSchemaReferences.objectProperty[leaf.text]
- ) {
- return 'cursor-pointer text-blue-400 hover:text-blue-300 decoration-blue-500/30 hover:decoration-blue-500/50 underline underline-offset-4';
+ [
+ 'objectPropertyStartQuotes',
+ 'objectPropertyEndQuotes',
+ ].includes(leaf.syntaxPart?.type)
+ )
+ return 'text-blue-200';
+ if (['objectProperty'].includes(leaf.syntaxPart?.type)) {
+ const isJsonScope = jsonPathsWithJsonScope
+ .filter(
+ (jsonPathWithScope) =>
+ jsonPathWithScope.scope ===
+ JsonSchemaScope.TypeDefinition,
+ )
+ .map(
+ (jsonPathsWithJsonScope) =>
+ jsonPathsWithJsonScope.jsonPath,
+ )
+ .includes(leaf.syntaxPart?.parentJsonPath);
+ if (
+ isJsonScope &&
+ jsonSchemaReferences.objectProperty[leaf.text]
+ ) {
+ return 'cursor-pointer text-blue-400 hover:text-blue-300 decoration-blue-500/30 hover:decoration-blue-500/50 underline underline-offset-4';
+ }
+ return 'text-cyan-500';
}
- return 'text-cyan-500';
- }
- if (leaf.syntaxPart?.type === 'stringValue') {
- if (jsonSchemaReferences.stringValue[leaf.text]) {
- return 'cursor-pointer text-amber-300 hover:text-amber-300 decoration-amber-500/30 hover:decoration-amber-500/50 underline underline-offset-4';
+ if (leaf.syntaxPart?.type === 'stringValue') {
+ if (jsonSchemaReferences.stringValue[leaf.text]) {
+ return 'cursor-pointer text-amber-300 hover:text-amber-300 decoration-amber-500/30 hover:decoration-amber-500/50 underline underline-offset-4';
+ }
+ return 'text-lime-200';
}
- return 'text-lime-200';
- }
- if (
- [
- 'objectStartBracket',
- 'objectEndBracket',
- 'arrayComma',
- 'arrayStartBracket',
- 'arrayEndBracket',
- ].includes(leaf.syntaxPart?.type)
- )
- return 'text-slate-400';
- if (
- [
- 'numberValue',
- 'stringValue',
- 'booleanValue',
- 'nullValue',
- ].includes(leaf.syntaxPart?.type)
- )
- return 'text-lime-200';
- })();
-
- const link: null | string = (() =>
- jsonSchemaReferences?.[leaf.syntaxPart?.type]?.[leaf.text] ||
- null)();
-
- return (
- {
- /* istanbul ignore if : link cannot be null */
- if (!link) return;
- router.push(link);
- }}
- className={classnames('pb-2', textStyles, 'whitespace-pre')}
- title={leaf.syntaxPart?.type}
- {...attributes}
- >
- {children}
-
- );
- }}
- renderElement={(props: any) => {
- // This will be the path to the image element.
- const { element, children, attributes } = props;
- const path = ReactEditor.findPath(editor, element);
- const line = path[0] + 1;
- /* istanbul ignore else : no else block to test */
- if (element.type === 'paragraph') {
+ if (
+ [
+ 'objectStartBracket',
+ 'objectEndBracket',
+ 'arrayComma',
+ 'arrayStartBracket',
+ 'arrayEndBracket',
+ ].includes(leaf.syntaxPart?.type)
+ )
+ return 'text-slate-400';
+ if (
+ [
+ 'numberValue',
+ 'stringValue',
+ 'booleanValue',
+ 'nullValue',
+ ].includes(leaf.syntaxPart?.type)
+ )
+ return 'text-lime-200';
+
+ // Handle partial schema specific highlighting that might not match exactly
+ if (!leaf.syntaxPart?.type) {
+ // If no syntax part type, apply default white color for partial schemas
+ return isPartialSchema ? 'text-white' : undefined;
+ }
+ })();
+
+ const link: null | string = (() =>
+ jsonSchemaReferences?.[leaf.syntaxPart?.type]?.[leaf.text] ||
+ null)();
+
return (
{
+ /* istanbul ignore if : link cannot be null */
+ if (!link) return;
+ router.push(link);
+ }}
+ className={cn('pb-2', textStyles, 'whitespace-pre')}
+ title={leaf.syntaxPart?.type}
{...attributes}
>
-
- {children}
+ {children}
);
- }
- /* istanbul ignore next:
- * There is no other element type in the render function. Hence this will never be called.*/
- throw new Error(
- `unknown element.type [${element.type}] in render function`,
- );
- }}
- />
+ }}
+ renderElement={(props: any) => {
+ // This will be the path to the image element.
+ const { element, children, attributes } = props;
+ const path = ReactEditor.findPath(editor, element);
+ const line = path[0] + 1;
+ /* istanbul ignore else : no else block to test */
+ if (element.type === 'paragraph') {
+ return (
+
+
+ {children}
+
+ );
+ }
+ /* istanbul ignore next:
+ * There is no other element type in the render function. Hence this will never be called.*/
+ throw new Error(
+ `unknown element.type [${element.type}] in render function`,
+ );
+ }}
+ />
+
{validation === 'invalid' && (
-
not compliant to schema
-
+
)}
{validation === 'valid' && (
-
compliant to schema
-
+
)}
-
+
{
pre: ({ children }) => {
const language = children?.props?.className;
const isJsonCode = language === 'lang-json';
+ const isJsoncCode = language === 'lang-jsonc';
const code = children?.props?.children;
+
if (isJsonCode) {
return ;
}
- return (
-
-
- {code}
-
-
- );
+ if (isJsoncCode) {
+ return ;
+ }
+
+ // Use JsonEditor for regular code blocks
+ return ;
},
blockquote: {
component: ({ children }) => (
@@ -670,7 +653,7 @@ export function TableOfContentMarkdown({
);
},
- } /* eslint-enable */
+ }
: { component: () => null },
...hiddenElements(
'strong',
diff --git a/components/ui/alert.tsx b/components/ui/alert.tsx
new file mode 100644
index 000000000..719548083
--- /dev/null
+++ b/components/ui/alert.tsx
@@ -0,0 +1,68 @@
+/* eslint-disable linebreak-style */
+/* eslint-disable react/prop-types */
+import * as React from 'react';
+import { cva, type VariantProps } from 'class-variance-authority';
+
+import { cn } from '@/lib/utils';
+
+const alertVariants = cva(
+ 'relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current',
+ {
+ variants: {
+ variant: {
+ default: 'bg-card text-card-foreground',
+ destructive:
+ 'text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90',
+ },
+ },
+ defaultVariants: {
+ variant: 'default',
+ },
+ },
+);
+
+function Alert({
+ className,
+ variant,
+ ...props
+}: React.ComponentProps<'div'> & VariantProps) {
+ return (
+
+ );
+}
+
+function AlertTitle({ className, ...props }: React.ComponentProps<'div'>) {
+ return (
+
+ );
+}
+
+function AlertDescription({
+ className,
+ ...props
+}: React.ComponentProps<'div'>) {
+ return (
+
+ );
+}
+
+export { Alert, AlertTitle, AlertDescription };
diff --git a/cypress/components/JsonEditor.cy.tsx b/cypress/components/JsonEditor.cy.tsx
index b31e24396..8f61f958f 100644
--- a/cypress/components/JsonEditor.cy.tsx
+++ b/cypress/components/JsonEditor.cy.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable cypress/no-unnecessary-waiting */
import JsonEditor from '~/components/JsonEditor';
import React from 'react';
import mockNextRouter, { MockRouter } from '../plugins/mockNextRouterUtils';
@@ -95,35 +96,34 @@ describe('JSON Editor Component', () => {
// mount component
cy.mount();
- // check if copy img is visible
+ // check if copy img is visible initially
cy.get('[data-test="copy-clipboard-button"]')
.children('img')
- .should('have.length', 2)
- .first()
+ .should('have.length', 1)
.should('have.attr', 'src', '/icons/copy.svg')
.should('be.visible');
- // click on copy img
+ // click on copy button
cy.get('[data-test="copy-clipboard-button"]').click();
- // check if clipboard writeText is copied the correct code
+ // check if clipboard writeText is called with the correct code
cy.get('@clipboardWriteText').should(
'have.been.calledWith',
JSON.stringify(initialCode, null, 2) + '\n',
);
- // check if copied img is visible
+ // check if copied img is visible after clicking
cy.get('[data-test="copy-clipboard-button"]')
.children('img')
- .last()
+ .should('have.length', 1)
.should('have.attr', 'src', '/icons/copied.svg')
.should('be.visible');
// after 2 seconds, check if copy img is visible again
+ cy.wait(2100); // Wait slightly longer than the 2000ms timeout
cy.get('[data-test="copy-clipboard-button"]')
.children('img')
- .should('have.length', 2)
- .first()
+ .should('have.length', 1)
.should('have.attr', 'src', '/icons/copy.svg')
.should('be.visible');
});
@@ -213,4 +213,545 @@ describe('JSON Editor Component', () => {
cy.get('[data-test="json-editor"]').trigger('copy');
cy.get('@clipboardWriteText').should('have.been.calledWith', '');
});
+
+ // Test JSONC support with isJsonc prop
+ it('should render JSONC code correctly', () => {
+ const jsoncCode = `{
+ // This is a comment
+ "name": "test",
+ "value": 123
+}`;
+
+ cy.mount();
+
+ // Check that the badge shows "code" for regular JSONC
+ cy.get('[data-test="check-json-schema"]').contains('code');
+ });
+
+ // Test partial schema detection in JSONC
+ it('should detect and display partial schema correctly', () => {
+ const partialSchemaCode = `// partial schema
+{
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ }
+ }
+}`;
+
+ cy.mount();
+
+ // Check that the badge shows "part of schema" and has the schema icon
+ cy.get('[data-test="check-json-schema"]').contains('part of schema');
+ cy.get('[data-test="check-json-schema"] img').should(
+ 'have.attr',
+ 'src',
+ '/logo-white.svg',
+ );
+ });
+
+ // Test partial schema with block comment
+ it('should detect partial schema with block comment', () => {
+ const partialSchemaCode = `/* partial schema */
+{
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ }
+ }
+}`;
+
+ cy.mount();
+
+ // Check that the badge shows "part of schema"
+ cy.get('[data-test="check-json-schema"]').contains('part of schema');
+ });
+
+ // Test schema badge for JSON with $schema property
+ it('should show schema badge for JSON with $schema property', () => {
+ const schemaCode = `{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ }
+ }
+}`;
+
+ cy.mount();
+
+ // Check that the badge shows "schema" and has the schema icon
+ cy.get('[data-test="check-json-schema"]').contains('schema');
+ cy.get('[data-test="check-json-schema"] img').should(
+ 'have.attr',
+ 'src',
+ '/logo-white.svg',
+ );
+ });
+
+ // Test schema badge for JSON with meta isSchema flag
+ it('should show schema badge for JSON with meta isSchema flag', () => {
+ const schemaCode = `// props { "isSchema": true }
+{
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ }
+ }
+}`;
+
+ cy.mount();
+
+ // Check that the badge shows "schema"
+ cy.get('[data-test="check-json-schema"]').contains('schema');
+ });
+
+ // Test data badge for regular JSON without schema
+ it('should show data badge for regular JSON', () => {
+ const dataCode = `{
+ "name": "test",
+ "value": 123,
+ "active": true
+}`;
+
+ cy.mount();
+
+ // Check that the badge shows "data"
+ cy.get('[data-test="check-json-schema"]').contains('data');
+ });
+
+ // Test indented code with meta indent flag
+ it('should apply indentation with meta indent flag', () => {
+ const indentedCode = `// props { "indent": true }
+{
+ "name": "test"
+}`;
+
+ cy.mount();
+
+ // Check that the card has the indentation class
+ // The ml-10 class is applied to the Card component, not the Editable
+ cy.get('[data-test="json-editor"]')
+ .closest('.relative')
+ .should('have.class', 'ml-10');
+ });
+
+ // Test invalid JSON parsing
+ it('should handle invalid JSON gracefully', () => {
+ const invalidJson = `{
+ "name": "test",
+ "value": 123,
+ "unclosed": {
+}`;
+
+ cy.mount();
+
+ // Should still render without crashing
+ cy.get('[data-test="json-editor"]').should('exist');
+ // Should show data badge since it's not valid JSON
+ cy.get('[data-test="check-json-schema"]').contains('data');
+ });
+
+ // Test empty code
+ it('should handle empty code', () => {
+ cy.mount();
+
+ // Should still render without crashing
+ cy.get('[data-test="json-editor"]').should('exist');
+ });
+
+ // Test code with only whitespace
+ it('should handle whitespace-only code', () => {
+ cy.mount();
+
+ // Should still render without crashing
+ cy.get('[data-test="json-editor"]').should('exist');
+ });
+
+ // Test cut functionality (read-only editor, so this is mainly for coverage)
+ it('should handle cut event', () => {
+ // mock clipboard writeText
+ cy.window().then((win) => {
+ cy.stub(win.navigator.clipboard, 'writeText').as('clipboardWriteText');
+ // Mock getSelection to return some text
+ cy.stub(win, 'getSelection').returns({
+ toString: () => 'selected text',
+ });
+ });
+
+ cy.mount();
+
+ // Test that the component renders without errors
+ cy.get('[data-test="json-editor"]').should('exist');
+
+ // Note: Cut event is not typically triggered in read-only editors
+ // This test ensures the component handles the event handler properly
+ });
+
+ // Test selection and copy functionality
+ it('should handle text selection and copy', () => {
+ // mock clipboard writeText
+ cy.window().then((win) => {
+ cy.stub(win.navigator.clipboard, 'writeText').as('clipboardWriteText');
+ // Mock getSelection to return some text
+ cy.stub(win, 'getSelection').returns({
+ toString: () => 'selected text',
+ });
+ });
+
+ cy.mount();
+
+ // Trigger copy event
+ cy.get('[data-test="json-editor"]').trigger('copy');
+ cy.get('@clipboardWriteText').should(
+ 'have.been.calledWith',
+ 'selected text',
+ );
+ });
+
+ // Test click on non-link text
+ it('should handle click on non-link text', () => {
+ cy.mount();
+
+ // Click on regular text (should not navigate)
+ cy.get('[data-test="json-editor"] span').first().click();
+
+ // Should not have called router.push
+ cy.get('@routerPush').should('not.have.been.called');
+ });
+
+ // Test partial schema syntax highlighting
+ it('should apply syntax highlighting to partial schemas', () => {
+ const partialSchemaCode = `// partial schema
+{
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ }
+ }
+}`;
+
+ cy.mount();
+
+ // Check that the code is rendered (syntax highlighting applied)
+ cy.get('[data-test="json-editor"]').should('exist');
+ cy.get('[data-test="check-json-schema"]').contains('part of schema');
+ });
+
+ // Test array bracket syntax highlighting in partial schemas (covers lines 171-172)
+ it('should handle array brackets in partial schema syntax highlighting', () => {
+ const partialSchemaWithArrays = `// partial schema
+{
+ "type": "object",
+ "properties": {
+ "tags": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ }
+}`;
+
+ cy.mount(
+ ,
+ );
+
+ // Check that the code is rendered with array syntax highlighting
+ cy.get('[data-test="json-editor"]').should('exist');
+ cy.get('[data-test="check-json-schema"]').contains('part of schema');
+ });
+
+ // Test specific array bracket characters to ensure full coverage of mapping logic
+ it('should handle both opening and closing array brackets in partial schemas', () => {
+ const arrayBracketsTest = `// partial schema
+[
+ "item1",
+ "item2"
+]`;
+
+ cy.mount();
+
+ // Check that the code is rendered with array syntax highlighting
+ cy.get('[data-test="json-editor"]').should('exist');
+ cy.get('[data-test="check-json-schema"]').contains('part of schema');
+ });
+
+ // Test calculateNewDecorationsMap with explicit isPartialSchema=false parameter
+ it('should use full JSON parsing when isPartialSchema is explicitly false', () => {
+ const regularJson = `{
+ "name": "test",
+ "value": 123,
+ "array": [1, 2, 3]
+}`;
+
+ cy.mount();
+
+ // Check that the code is rendered with full JSON parsing
+ cy.get('[data-test="json-editor"]').should('exist');
+ cy.get('[data-test="check-json-schema"]').contains('data');
+ });
+
+ // Test full JSON parsing for non-partial schemas (covers line 194)
+ it('should use full JSON parsing for complete schemas', () => {
+ const completeSchema = `{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ }
+}`;
+
+ cy.mount();
+
+ // Check that the code is rendered with full JSON parsing
+ cy.get('[data-test="json-editor"]').should('exist');
+ cy.get('[data-test="check-json-schema"]').contains('schema');
+ });
+
+ // Test meta props with invalid JSON
+ it('should handle invalid meta props JSON', () => {
+ const invalidMetaProps =
+ '// props { "valid": true, "caption": "test" }\n{ "test": "value" }';
+
+ cy.mount();
+
+ // Should still render without crashing
+ cy.get('[data-test="json-editor"]').should('exist');
+ });
+
+ // Test meta props with missing groups
+ it('should handle meta props with missing groups', () => {
+ const metaPropsWithoutGroups = '// props {}\n{ "test": "value" }';
+
+ cy.mount();
+
+ // Should still render without crashing
+ cy.get('[data-test="json-editor"]').should('exist');
+ });
+
+ // Test code caption without meta
+ it('should handle code without caption', () => {
+ cy.mount();
+
+ // Should render without caption
+ cy.get('[data-test="code-caption"]').should('exist');
+ });
+
+ // Test validation without meta
+ it('should handle code without validation meta', () => {
+ cy.mount();
+
+ // Should not show validation alerts
+ cy.get('[data-test="compliant-to-schema"]').should('not.exist');
+ cy.get('[data-test="not-compliant-to-schema"]').should('not.exist');
+ });
+
+ // ===== REGULAR CODE BLOCK TESTS =====
+
+ // Test regular code block rendering with language and code props
+ it('should render regular code block with language and code props', () => {
+ const testCode = `function hello() {
+ console.log("Hello, World!");
+}`;
+
+ cy.mount();
+
+ // Should render the code block (not the JSON editor)
+ cy.get('[data-test="json-editor"]').should('not.exist');
+
+ // Should show the copy button
+ cy.get('button').should('be.visible');
+
+ // Should show the language badge
+ cy.get('.bg-white\\/20').contains('javascript');
+ });
+
+ // Test regular code block copy functionality
+ it('should copy regular code block text when copy button is clicked', () => {
+ const testCode = `function hello() {
+ console.log("Hello, World!");
+}`;
+
+ // mock clipboard writeText
+ cy.window().then((win) => {
+ cy.stub(win.navigator.clipboard, 'writeText').as('clipboardWriteText');
+ });
+
+ cy.mount();
+
+ // Click on copy button
+ cy.get('button').click();
+
+ // Check if clipboard writeText is called with the correct code
+ cy.get('@clipboardWriteText').should('have.been.calledWith', testCode);
+
+ // Check if copied icon is visible after clicking
+ cy.get('button img').should('have.attr', 'src', '/icons/copied.svg');
+
+ // After 2 seconds, check if copy icon is visible again
+ cy.wait(2100);
+ cy.get('button img').should('have.attr', 'src', '/icons/copy.svg');
+ });
+
+ // Test regular code block with different languages
+ it('should display correct language badge for different languages', () => {
+ const testCode = 'const x = 1;';
+
+ // Test JavaScript
+ cy.mount();
+ cy.get('.bg-white\\/20').contains('javascript');
+
+ // Test Python
+ cy.mount();
+ cy.get('.bg-white\\/20').contains('python');
+
+ // Test TypeScript
+ cy.mount();
+ cy.get('.bg-white\\/20').contains('typescript');
+ });
+
+ // Test regular code block without language
+ it('should display "code" badge when no language is provided', () => {
+ const testCode = 'some random code';
+
+ cy.mount();
+
+ // Should show "code" as the badge text
+ cy.get('.bg-white\\/20').contains('code');
+ });
+
+ // Test regular code block with empty code
+ it('should handle empty code in regular code block', () => {
+ cy.mount();
+
+ // Should still render without crashing
+ cy.get('button').should('be.visible');
+ cy.get('.bg-white\\/20').contains('javascript');
+ });
+
+ // Test regular code block with whitespace-only code
+ it('should handle whitespace-only code in regular code block', () => {
+ cy.mount();
+
+ // Should still render without crashing
+ cy.get('button').should('be.visible');
+ cy.get('.bg-white\\/20').contains('javascript');
+ });
+
+ // Test regular code block syntax highlighting
+ it('should apply syntax highlighting to regular code blocks', () => {
+ const testCode = `function hello() {
+ console.log("Hello, World!");
+ return true;
+}`;
+
+ cy.mount();
+
+ // Should render the code with syntax highlighting
+ // The Highlight component should be present
+ cy.get('.overflow-x-auto').should('exist');
+
+ // Should show the copy button and badge
+ cy.get('button').should('be.visible');
+ cy.get('.bg-white\\/20').contains('javascript');
+ });
+
+ // Test regular code block with complex code
+ it('should handle complex code in regular code blocks', () => {
+ const complexCode = `import React from 'react';
+
+interface Props {
+ name: string;
+ age: number;
+}
+
+const Component: React.FC = ({ name, age }) => {
+ const [count, setCount] = React.useState(0);
+
+ React.useEffect(() => {
+ console.log(\`Hello \${name}, you are \${age} years old\`);
+ }, [name, age]);
+
+ return (
+
+
Hello {name}!
+
Count: {count}
+
+
+ );
+};
+
+export default Component;`;
+
+ cy.mount();
+
+ // Should render the complex code without crashing
+ cy.get('button').should('be.visible');
+ cy.get('.bg-white\\/20').contains('typescript');
+ });
+
+ // Test that JSON mode and regular code mode are mutually exclusive
+ it('should prioritize JSON mode when both initialCode and code are provided', () => {
+ const jsonCode = '{"test": "value"}';
+ const regularCode = 'console.log("test");';
+
+ cy.mount(
+ ,
+ );
+
+ // Should render in JSON mode (with JSON editor)
+ cy.get('[data-test="json-editor"]').should('exist');
+ cy.get('[data-test="check-json-schema"]').contains('data');
+ });
+
+ // Test regular code block with special characters
+ it('should handle special characters in regular code blocks', () => {
+ const specialCode =
+ 'const special = "Hello & World! < > " \' \\n \\t \\r";';
+
+ cy.mount();
+
+ // Should render without crashing
+ cy.get('button').should('be.visible');
+ cy.get('.bg-white\\/20').contains('javascript');
+ });
+
+ // Test regular code block copy functionality with special characters
+ it('should copy code with special characters correctly', () => {
+ const specialCode =
+ 'const special = "Hello & World! < > " \' \\n \\t \\r";';
+
+ // mock clipboard writeText
+ cy.window().then((win) => {
+ cy.stub(win.navigator.clipboard, 'writeText').as('clipboardWriteText');
+ });
+
+ cy.mount();
+
+ // Click on copy button
+ cy.get('button').click();
+
+ // Check if clipboard writeText is called with the correct code
+ cy.get('@clipboardWriteText').should('have.been.calledWith', specialCode);
+ });
});
diff --git a/cypress/components/ui/alert.cy.tsx b/cypress/components/ui/alert.cy.tsx
new file mode 100644
index 000000000..44d3d3872
--- /dev/null
+++ b/cypress/components/ui/alert.cy.tsx
@@ -0,0 +1,214 @@
+import React from 'react';
+import { Alert, AlertTitle, AlertDescription } from '@/components/ui/alert';
+
+describe('Alert Component', () => {
+ it('renders basic Alert component correctly', () => {
+ cy.mount(This is a basic alert message);
+
+ cy.get('[data-slot="alert"]').should('exist');
+ cy.get('[role="alert"]').should('exist');
+ cy.get('[data-slot="alert"]').contains('This is a basic alert message');
+ });
+
+ it('renders Alert with default variant correctly', () => {
+ cy.mount(Default alert message);
+
+ cy.get('[data-slot="alert"]').should('exist');
+ cy.get('[data-slot="alert"]').should('have.class', 'bg-card');
+ cy.get('[data-slot="alert"]').should('have.class', 'text-card-foreground');
+ });
+
+ it('renders Alert with destructive variant correctly', () => {
+ cy.mount(Destructive alert message);
+
+ cy.get('[data-slot="alert"]').should('exist');
+ cy.get('[data-slot="alert"]').should('have.class', 'text-destructive');
+ cy.get('[data-slot="alert"]').should('have.class', 'bg-card');
+ });
+
+ it('renders Alert with custom className correctly', () => {
+ cy.mount(
+ Alert with custom class,
+ );
+
+ cy.get('[data-slot="alert"]').should('have.class', 'custom-alert-class');
+ });
+
+ it('renders Alert with AlertTitle correctly', () => {
+ cy.mount(
+
+ Alert Title
+ This is the alert description
+ ,
+ );
+
+ cy.get('[data-slot="alert-title"]').should('exist');
+ cy.get('[data-slot="alert-title"]').contains('Alert Title');
+ cy.get('[data-slot="alert-title"]').should('have.class', 'font-medium');
+ });
+
+ it('renders Alert with AlertDescription correctly', () => {
+ cy.mount(
+
+ This is an alert description
+ ,
+ );
+
+ cy.get('[data-slot="alert-description"]').should('exist');
+ cy.get('[data-slot="alert-description"]').contains(
+ 'This is an alert description',
+ );
+ cy.get('[data-slot="alert-description"]').should(
+ 'have.class',
+ 'text-muted-foreground',
+ );
+ });
+
+ it('renders Alert with both AlertTitle and AlertDescription correctly', () => {
+ cy.mount(
+
+ Important Notice
+
+ This is a detailed description of the alert message.
+
+ ,
+ );
+
+ cy.get('[data-slot="alert-title"]').should('exist');
+ cy.get('[data-slot="alert-title"]').contains('Important Notice');
+ cy.get('[data-slot="alert-description"]').should('exist');
+ cy.get('[data-slot="alert-description"]').contains(
+ 'This is a detailed description of the alert message.',
+ );
+ });
+
+ it('renders Alert with icon correctly', () => {
+ cy.mount(
+
+
+ Alert with Icon
+ This alert has an icon
+ ,
+ );
+
+ cy.get('[data-testid="alert-icon"]').should('exist');
+ cy.get('[data-slot="alert"]').should(
+ 'have.class',
+ 'has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr]',
+ );
+ });
+
+ it('renders destructive Alert with icon correctly', () => {
+ cy.mount(
+
+
+ Destructive Alert
+
+ This is a destructive alert with icon
+
+ ,
+ );
+
+ cy.get('[data-testid="destructive-icon"]').should('exist');
+ cy.get('[data-slot="alert"]').should('have.class', 'text-destructive');
+ // Check that the alert description has the data-slot attribute and is styled appropriately
+ cy.get('[data-slot="alert-description"]').should('exist');
+ cy.get('[data-slot="alert-description"]').should(
+ 'have.attr',
+ 'data-slot',
+ 'alert-description',
+ );
+ });
+
+ it('renders AlertTitle with custom className correctly', () => {
+ cy.mount(
+
+ Custom Title
+ ,
+ );
+
+ cy.get('[data-slot="alert-title"]').should(
+ 'have.class',
+ 'custom-title-class',
+ );
+ });
+
+ it('renders AlertDescription with custom className correctly', () => {
+ cy.mount(
+
+
+ Custom Description
+
+ ,
+ );
+
+ cy.get('[data-slot="alert-description"]').should(
+ 'have.class',
+ 'custom-description-class',
+ );
+ });
+
+ it('renders multiple Alert components correctly', () => {
+ cy.mount(
+
+
+ First Alert
+ First alert description
+
+
+ Second Alert
+ Second alert description
+
+
,
+ );
+
+ cy.get('[data-testid="alert-1"]').should('exist');
+ cy.get('[data-testid="alert-1"] [data-slot="alert-title"]').contains(
+ 'First Alert',
+ );
+ cy.get('[data-testid="alert-2"]').should('exist');
+ cy.get('[data-testid="alert-2"] [data-slot="alert-title"]').contains(
+ 'Second Alert',
+ );
+ cy.get('[data-testid="alert-2"]').should('have.class', 'text-destructive');
+ });
+
+ it('renders Alert with HTML content correctly', () => {
+ cy.mount(
+
+ HTML Content Alert
+
+ This alert contains bold text and{' '}
+ italic text.
+
+ ,
+ );
+
+ cy.get('[data-slot="alert-description"]').contains('bold text');
+ cy.get('[data-slot="alert-description"]').contains('italic text');
+ cy.get('[data-slot="alert-description"] strong').should('exist');
+ cy.get('[data-slot="alert-description"] em').should('exist');
+ });
+
+ it('renders Alert with accessibility attributes correctly', () => {
+ cy.mount(
+
+ Accessible Alert
+
+ This alert has custom accessibility attributes
+
+ ,
+ );
+
+ cy.get('[data-testid="accessible-alert"]').should(
+ 'have.attr',
+ 'aria-label',
+ 'Custom alert',
+ );
+ cy.get('[data-testid="accessible-alert"]').should(
+ 'have.attr',
+ 'role',
+ 'alert',
+ );
+ });
+});
diff --git a/pages/learn/getting-started-step-by-step/getting-started-step-by-step.md b/pages/learn/getting-started-step-by-step/getting-started-step-by-step.md
index 1717c9d28..78ca3e695 100644
--- a/pages/learn/getting-started-step-by-step/getting-started-step-by-step.md
+++ b/pages/learn/getting-started-step-by-step/getting-started-step-by-step.md
@@ -103,6 +103,7 @@ To add the `properties` object to the schema:
1. Add the `properties` validation keyword to the end of the schema:
```jsonc
+ // partial schema
...
"title": "Product",
"description": "A product from Acme's catalog",
@@ -117,6 +118,7 @@ To add the `properties` object to the schema:
* `type`: defines what kind of data is expected. For this example, since the product identifier is a numeric value, use `integer`.
```jsonc
+ // partial schema
...
"properties": {
"productId": {
@@ -177,6 +179,7 @@ To define a required property:
1. Inside the `properties` object, add the `price` key. Include the usual schema annotations `description` and `type`, where `type` is a number:
```jsonc
+ // partial schema
"properties": {
...
"price": {
@@ -189,6 +192,7 @@ To define a required property:
2. Add the `exclusiveMinimum` validation keyword and set the value to zero:
```jsonc
+ // partial schema
"price": {
"description": "The price of the product",
"type": "number",
@@ -199,6 +203,7 @@ To define a required property:
3. Add the `required` validation keyword to the end of the schema, after the `properties` object. Add `productID`, `productName`, and the new `price` key to the array:
```jsonc
+ // partial schema
...
"properties": {
...
@@ -255,6 +260,7 @@ To define an optional property:
1. Inside the `properties` object, add the `tags` keyword. Include the usual schema annotations `description` and `type`, and define `type` as an array:
```jsonc
+ // partial schema
...
"properties": {
...
@@ -268,6 +274,7 @@ To define an optional property:
2. Add a new validation keyword for `items` to define what appears in the array. For example, `string`:
```jsonc
+ // partial schema
...
"tags": {
"description": "Tags for the product",
@@ -281,6 +288,7 @@ To define an optional property:
3. To make sure there is at least one item in the array, use the `minItems` validation keyword:
```jsonc
+ // partial schema
...
"tags": {
"description": "Tags for the product",
@@ -295,6 +303,7 @@ To define an optional property:
4. To make sure that every item in the array is unique, use the `uniqueItems` validation keyword and set it to `true`:
```jsonc
+ // partial schema
...
"tags": {
"description": "Tags for the product",
@@ -357,6 +366,7 @@ To create a nested data structure:
1. Inside the `properties` object, create a new key called `dimensions`:
```jsonc
+ // partial schema
...
"properties": {
...
@@ -367,6 +377,7 @@ To create a nested data structure:
2. Define the `type` validation keyword as `object`:
```jsonc
+ // partial schema
...
"dimensions": {
"type": "object"
@@ -376,6 +387,7 @@ To create a nested data structure:
3. Add the `properties` validation keyword to contain the nested data structure. Inside the new `properties` keyword, add keywords for `length`, `width`, and `height` that all use the `number` type:
```jsonc
+ // partial schema
...
"dimensions": {
"type": "object",
@@ -396,6 +408,7 @@ To create a nested data structure:
4. To make each of these properties required, add a `required` validation keyword inside the `dimensions` object:
```jsonc
+ // partial schema
...
"dimensions": {
"type": "object",
@@ -504,6 +517,7 @@ To reference this schema in the product catalog schema:
1. Inside the `properties` object, add a key named `warehouseLocation`:
```jsonc
+ // partial schema
...
"properties": {
...
@@ -514,6 +528,7 @@ To reference this schema in the product catalog schema:
2. To link to the external geographical location schema, add the `$ref` schema keyword and the schema URL:
```jsonc
+ // partial schema
...
"warehouseLocation": {
"description": "Coordinates of the warehouse where the product is located.",
diff --git a/pages/understanding-json-schema/reference/non_json_data.md b/pages/understanding-json-schema/reference/non_json_data.md
index 0f401002b..934ce089b 100644
--- a/pages/understanding-json-schema/reference/non_json_data.md
+++ b/pages/understanding-json-schema/reference/non_json_data.md
@@ -79,7 +79,7 @@ To better understand how `contentEncoding` and `contentMediaType` are applied in

-->
-```mermaid
+```code
block-beta
columns 9
A space B space C space D space E