Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tests: add tests for some components #12108

Merged
merged 10 commits into from
May 3, 2024
2 changes: 1 addition & 1 deletion kolibri/core/assets/src/views/TimeDuration.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<template>

<KOptionalText
:text="seconds ? formattedTime : ''"
:text="seconds !== null ? formattedTime : ''"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Made this change as earlier passing 0 as seconds led to this expression being evaluated as false, and thus an empty string being displayed instead of 0 seconds. Also added a test case related to the same.

/>

</template>
Expand Down
60 changes: 60 additions & 0 deletions kolibri/core/assets/src/views/__tests__/FocusTrap.spec.js
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not very sure if the way I have chosen is the most user-centric way to test this FocusTrap component. Would appreciate any feedback on the same.

Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { render } from '@testing-library/vue';
import userEvent from '@testing-library/user-event';
import VueRouter from 'vue-router';
import FocusTrap from '../FocusTrap.vue';

const renderComponent = (props = {}) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The FocusTrap tests look good.

Not a blocker -- the only thing I could think that might be worth adding is a call to the public reset method on FocusTrap to see that it updates isTrapActive and such.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it! That's an important use case. Will add tests for it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have added a test suite to cover this use-case, but I can't get it to work. Like to access the public method reset of the component, we would need to add a wrapper around it so that the user can access the same. I tried writing a test suite with the same, but can't get it to work. Would highly appreciate any help with the same.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a case where the 'user' of the component is actually the developer, so I don't think it's necessary to do this via DOM interaction, and the reset method can be invoked programmatically - as it forms part of the public API of the component.

return render(FocusTrap, {
props: {
disabled: false,
...props,
},
routes: new VueRouter(),
});
};

describe('FocusTrap', () => {
it('should emit the "shouldFocusFirstEl" element when the tab key is pressed once', async () => {
const { emitted } = renderComponent();

await userEvent.tab();
expect(emitted()).toHaveProperty('shouldFocusFirstEl');
expect(emitted().shouldFocusFirstEl.length).toBe(1);
});

it("should trap the focus ('shouldFocusFirstEl' should be emitted twice) on pressing tab twice ", async () => {
EshaanAgg marked this conversation as resolved.
Show resolved Hide resolved
const { emitted } = renderComponent();

await userEvent.tab();
await userEvent.tab();

expect(emitted()).toHaveProperty('shouldFocusFirstEl');
expect(emitted().shouldFocusFirstEl.length).toBe(2);
});

it('should emit "shouldFocusLastEl" when the element is subsequently focused after the inital focus', async () => {
EshaanAgg marked this conversation as resolved.
Show resolved Hide resolved
const { emitted } = renderComponent();

await userEvent.tab();
await userEvent.tab();

// Shift + Tab is used to focus on the initial element again
await userEvent.tab({ shift: true });

expect(emitted()).toHaveProperty('shouldFocusLastEl');
expect(emitted().shouldFocusLastEl.length).toBe(1);
});

it("should not trap focus when 'disabled' prop is set to true", async () => {
const { emitted } = renderComponent({ disabled: true });

await userEvent.tab();
expect(emitted()).not.toHaveProperty('shouldFocusFirstEl');

await userEvent.tab();
expect(emitted()).not.toHaveProperty('shouldFocusFirstEl');

await userEvent.tab({ shift: true });
expect(emitted()).not.toHaveProperty('shouldFocusLastEl');
});
});
57 changes: 57 additions & 0 deletions kolibri/core/assets/src/views/__tests__/TimeDuration.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { render, screen } from '@testing-library/vue';
import VueRouter from 'vue-router';
import TimeDuration from '../TimeDuration.vue';

const renderComponent = (props = {}) => {
return render(TimeDuration, {
props,
routes: new VueRouter(),
});
};

const MINUTE = 60;
const HOUR = 60 * MINUTE;
const DAY = 24 * HOUR;

const testCases = [
// Under 2 minutes, should show seconds
{ seconds: 0, expected: '0 seconds' },
EshaanAgg marked this conversation as resolved.
Show resolved Hide resolved
{ seconds: 1, expected: '1 second' },
{ seconds: 59, expected: '59 seconds' },
{ seconds: MINUTE, expected: '60 seconds' },
{ seconds: 2 * MINUTE - 1, expected: '119 seconds' },

// Under 1 hour, should show minutes (rounded down)
{ seconds: 2 * MINUTE, expected: '2 minutes' },
{ seconds: 30 * MINUTE, expected: '30 minutes' },
{ seconds: 30 * MINUTE + 1, expected: '30 minutes' },
{ seconds: 30 * MINUTE + 59, expected: '30 minutes' },
{ seconds: 59 * MINUTE, expected: '59 minutes' },

// Under 1 day, should show hours (rounded down)
{ seconds: HOUR, expected: '1 hour' },
{ seconds: 2 * HOUR, expected: '2 hours' },
{ seconds: 23 * HOUR, expected: '23 hours' },
{ seconds: 23 * HOUR + 59 * MINUTE, expected: '23 hours' },
{ seconds: 23 * HOUR + 59 * MINUTE + 59, expected: '23 hours' },

// Over 1 day, should show days (rounded down)
{ seconds: DAY, expected: '1 day' },
{ seconds: 2 * DAY, expected: '2 days' },
{ seconds: 6 * DAY, expected: '6 days' },
{ seconds: 6 * DAY + 23 * HOUR + 59 * MINUTE + 59, expected: '6 days' },
];

describe('TimeDuration', () => {
testCases.forEach(({ seconds, expected }) => {
it(`should render ${expected} for ${seconds} seconds`, () => {
renderComponent({ seconds });
expect(screen.getByText(expected)).toBeInTheDocument();
});
});
EshaanAgg marked this conversation as resolved.
Show resolved Hide resolved

it('should render empty string if seconds are not provided as props', () => {
renderComponent();
expect(screen.getByText('—')).toBeInTheDocument();
});
});
45 changes: 45 additions & 0 deletions kolibri/core/assets/src/views/__tests__/UserTypeDisplay.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import VueRouter from 'vue-router';
import { render, screen } from '@testing-library/vue';
import UserTypeDisplay from '../UserTypeDisplay.vue';

const sampleUserType = 'testing-user-type';
const translatedSampleUserType = 'Testing User Type';
EshaanAgg marked this conversation as resolved.
Show resolved Hide resolved

// Helper function to render the component with the provided props
const renderComponent = props => {
const translatedUserKinds = {
computed: {
typeDisplayMap() {
return {
[sampleUserType]: translatedSampleUserType,
};
},
},
};

return render(UserTypeDisplay, {
routes: new VueRouter(),
props: {
userType: sampleUserType,
...props,
},
mixins: [translatedUserKinds],
});
};

describe('UserTypeDisplay', () => {
test('smoke test (renders the translated user type correctly)', () => {
renderComponent({ userType: sampleUserType });
expect(screen.getByText(translatedSampleUserType)).toBeInTheDocument();
});

test('does not render the untranslated user type', () => {
renderComponent({ userType: sampleUserType });
expect(screen.queryByText(sampleUserType)).not.toBeInTheDocument();
});

test('does not render anything if the userType prop is not provided', () => {
const { container } = renderComponent({ userType: undefined });
expect(container).toBeEmptyDOMElement();
});
});