Skip to content
Open
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
@@ -0,0 +1,53 @@
import type {i18n} from 'i18next';
import {html} from 'lit';
import {beforeAll, describe, expect, it} from 'vitest';
import {renderFunctionFixture} from '@/vitest-utils/testing-helpers/fixture';
import {createTestI18n} from '@/vitest-utils/testing-helpers/i18n-utils';
import {renderSnippetFooter, type SnippetFooterProps} from './snippet-footer';

describe('#renderSnippetFooter', () => {
let i18n: i18n;

beforeAll(async () => {
i18n = await createTestI18n();
});

const renderComponent = async (
props: Partial<SnippetFooterProps> = {},
children = html``
) => {
const element = await renderFunctionFixture(
html`${renderSnippetFooter({
props: {
i18n,
...props,
},
})(children)}`
);

return element.querySelector('footer') as HTMLElement;
};

it('should render a footer element', async () => {
const footer = await renderComponent({});
expect(footer).toBeInTheDocument();
});

it('should render with footer part', async () => {
const footer = await renderComponent({});
expect(footer.getAttribute('part')).toBe('footer');
});

it('should render with aria-label', async () => {
const footer = await renderComponent({});
expect(footer.getAttribute('aria-label')).toBe(
i18n.t('smart-snippet-source')
);
});

it('should render children inside the footer', async () => {
const children = html`<p>Footer Content</p>`;
const footer = await renderComponent({}, children);
expect(footer?.textContent).toContain('Footer Content');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type {i18n} from 'i18next';
import {html} from 'lit';
import type {FunctionalComponentWithChildren} from '@/src/utils/functional-component-utils';

export interface SnippetFooterProps {
i18n: i18n;
}

export const renderSnippetFooter: FunctionalComponentWithChildren<
SnippetFooterProps
> =
({props}) =>
(children) => {
return html`<footer
part="footer"
aria-label=${props.i18n.t('smart-snippet-source')}
>
${children}
</footer>`;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import {html} from 'lit';
import {describe, expect, it} from 'vitest';
import {renderFunctionFixture} from '@/vitest-utils/testing-helpers/fixture';
import {
renderSnippetQuestion,
type SnippetQuestionProps,
} from './snippet-question';

describe('#renderSnippetQuestion', () => {
const renderComponent = async (props: Partial<SnippetQuestionProps> = {}) => {
const element = await renderFunctionFixture(
html`${renderSnippetQuestion({
props: {
question: 'What is a smart snippet?',
headingLevel: undefined,
...props,
},
})}`
);

return element.firstElementChild as HTMLElement;
};

it('should render the question text', async () => {
const question = await renderComponent({
question: 'What is a smart snippet?',
});
expect(question.textContent?.trim()).toBe('What is a smart snippet?');
});

it('should render with text-xl and font-bold classes', async () => {
const question = await renderComponent({});
expect(question).toHaveClass('text-xl');
expect(question).toHaveClass('font-bold');
});

it('should render with the question part', async () => {
const question = await renderComponent({});
expect(question.getAttribute('part')).toBe('question');
});

it('should render as a div when headingLevel is undefined', async () => {
const question = await renderComponent({headingLevel: undefined});
expect(question.tagName.toLowerCase()).toBe('div');
});

it('should render as a div when headingLevel is 0', async () => {
const question = await renderComponent({headingLevel: 0});
expect(question.tagName.toLowerCase()).toBe('div');
});

it('should render with incremented heading level', async () => {
const question = await renderComponent({headingLevel: 2});
expect(question.tagName.toLowerCase()).toBe('h3');
});

it('should render as h2 when headingLevel is 1', async () => {
const question = await renderComponent({headingLevel: 1});
expect(question.tagName.toLowerCase()).toBe('h2');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import {html} from 'lit';
import type {FunctionalComponent} from '@/src/utils/functional-component-utils';
import {renderHeading} from '../../heading';

export interface SnippetQuestionProps {
headingLevel?: number;
question: string;
}

export const renderSnippetQuestion: FunctionalComponent<
SnippetQuestionProps
> = ({props}) => {
return renderHeading({
props: {
level: props.headingLevel ? props.headingLevel + 1 : 0,
class: 'text-xl font-bold',
part: 'question',
},
})(html`${props.question}`);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import {html} from 'lit';
import {describe, expect, it} from 'vitest';
import {renderFunctionFixture} from '@/vitest-utils/testing-helpers/fixture';
import {
renderSnippetTruncatedAnswer,
type SnippetTruncatedAnswerProps,
} from './snippet-truncated-answer';

describe('#renderSnippetTruncatedAnswer', () => {
const renderComponent = async (
props: Partial<SnippetTruncatedAnswerProps> = {}
) => {
const element = await renderFunctionFixture(
html`${renderSnippetTruncatedAnswer({
props: {
answer: '<p>This is the answer</p>',
style: undefined,
...props,
},
})}`
);

return element.firstElementChild as HTMLElement;
};

it('should render a div with truncated-answer part', async () => {
const answer = await renderComponent({});
expect(answer).toBeInTheDocument();
expect(answer.getAttribute('part')).toBe('truncated-answer');
});

it('should render atomic-smart-snippet-answer component', async () => {
const answer = await renderComponent({});
const answerElement = answer.querySelector('atomic-smart-snippet-answer');
expect(answerElement).toBeInTheDocument();
});

it('should set htmlContent property on atomic-smart-snippet-answer', async () => {
const answer = await renderComponent({answer: '<p>Test answer</p>'});
const answerElement = answer.querySelector(
'atomic-smart-snippet-answer'
// biome-ignore lint/suspicious/noExplicitAny: testing property access on custom element
) as any;
expect(answerElement.htmlContent).toBe('<p>Test answer</p>');
});

it('should set innerStyle property when style is provided', async () => {
const answer = await renderComponent({style: 'color: red;'});
const answerElement = answer.querySelector(
'atomic-smart-snippet-answer'
// biome-ignore lint/suspicious/noExplicitAny: testing property access on custom element
) as any;
expect(answerElement.innerStyle).toBe('color: red;');
});

it('should set exportparts attribute', async () => {
const answer = await renderComponent({});
const answerElement = answer.querySelector('atomic-smart-snippet-answer');
expect(answerElement?.getAttribute('exportparts')).toBe('answer');
});

it('should set part attribute to body', async () => {
const answer = await renderComponent({});
const answerElement = answer.querySelector('atomic-smart-snippet-answer');
expect(answerElement?.getAttribute('part')).toBe('body');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import {html} from 'lit';
import {ifDefined} from 'lit/directives/if-defined.js';
import type {FunctionalComponent} from '@/src/utils/functional-component-utils';

export interface SnippetTruncatedAnswerProps {
answer: string;
style?: string;
}

export const renderSnippetTruncatedAnswer: FunctionalComponent<
SnippetTruncatedAnswerProps
> = ({props}) => {
return html`<div part="truncated-answer">
<atomic-smart-snippet-answer
exportparts="answer"
part="body"
.htmlContent=${props.answer}
.innerStyle=${ifDefined(props.style)}
></atomic-smart-snippet-answer>
</div>`;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import type {i18n} from 'i18next';
import {html} from 'lit';
import {beforeAll, describe, expect, it} from 'vitest';
import {renderFunctionFixture} from '@/vitest-utils/testing-helpers/fixture';
import {createTestI18n} from '@/vitest-utils/testing-helpers/i18n-utils';
import {
renderSnippetWrapper,
type SnippetWrapperProps,
} from './snippet-wrapper';

describe('#renderSnippetWrapper', () => {
let i18n: i18n;

beforeAll(async () => {
i18n = await createTestI18n();
});

const renderComponent = async (
props: Partial<SnippetWrapperProps> = {},
children = html``
) => {
const element = await renderFunctionFixture(
html`${renderSnippetWrapper({
props: {
i18n,
headingLevel: undefined,
...props,
},
})(children)}`
);

return element.querySelector('aside') as HTMLElement;
};

it('should render an aside element with aria-label', async () => {
const aside = await renderComponent({});
expect(aside).toBeInTheDocument();
expect(aside.getAttribute('aria-label')).toBe(i18n.t('smart-snippet'));
});

it('should render an article element with the smart-snippet part', async () => {
const aside = await renderComponent({});
const article = aside.querySelector('article[part="smart-snippet"]');
expect(article).toBeInTheDocument();
});

it('should render with background and border classes', async () => {
const aside = await renderComponent({});
const article = aside.querySelector('article');
expect(article).toHaveClass('bg-background');
expect(article).toHaveClass('border-neutral');
expect(article).toHaveClass('text-on-background');
expect(article).toHaveClass('rounded-lg');
expect(article).toHaveClass('border');
});

it('should render a heading with sr-only class', async () => {
const aside = await renderComponent({});
const heading = aside.querySelector('.sr-only');
expect(heading).toBeInTheDocument();
expect(heading?.textContent?.trim()).toBe(i18n.t('smart-snippet'));
});

it('should render heading with level 0 when headingLevel is undefined', async () => {
const aside = await renderComponent({headingLevel: undefined});
const heading = aside.querySelector('div.sr-only');
expect(heading).toBeInTheDocument();
});

it('should render heading with specified level', async () => {
const aside = await renderComponent({headingLevel: 2});
const heading = aside.querySelector('h2.sr-only');
expect(heading).toBeInTheDocument();
});

it('should render children inside the article', async () => {
const children = html`<p>Test Content</p>`;
const aside = await renderComponent({}, children);
const article = aside.querySelector('article');
expect(article?.textContent).toContain('Test Content');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type {i18n} from 'i18next';
import {html} from 'lit';
import type {FunctionalComponentWithChildren} from '@/src/utils/functional-component-utils';
import {renderHeading} from '../../heading';

export interface SnippetWrapperProps {
headingLevel?: number;
i18n: i18n;
}

export const renderSnippetWrapper: FunctionalComponentWithChildren<
SnippetWrapperProps
> =
({props}) =>
(children) => {
return html`<aside aria-label=${props.i18n.t('smart-snippet')}>
${renderHeading({
props: {level: props.headingLevel ?? 0, class: 'sr-only'},
})(html`${props.i18n.t('smart-snippet')}`)}
<article
class="bg-background border-neutral text-on-background rounded-lg border p-6 pb-4"
part="smart-snippet"
>
${children}
</article>
</aside>`;
};
Loading