* Dynamically render forward text in Navigation component * Render either "Skip for now" or "Continue" on the FollowTags, ProfileForm, and FollowUsers components. * Refactor the ProfileForm component to dynamically change the button text based on the form's state. * Render specific button text for last onboarding slide * Fix back arrow styles for accessibility Also set an explicit width on the containing button around the svg. * Use min-width to specify back-button size
410 lines
12 KiB
JavaScript
410 lines
12 KiB
JavaScript
import { h } from 'preact';
|
|
import { deep } from 'preact-render-spy';
|
|
import fetch from 'jest-fetch-mock';
|
|
import { axe, toHaveNoViolations } from 'jest-axe';
|
|
|
|
import Onboarding from '../Onboarding';
|
|
import ProfileForm from '../components/ProfileForm';
|
|
import FollowTags from '../components/FollowTags';
|
|
import FollowUsers from '../components/FollowUsers';
|
|
|
|
global.fetch = fetch;
|
|
|
|
// Corresponds to the order of slides declared in the Onboarding component.
|
|
const slides = [
|
|
'IntroSlide',
|
|
'FollowTags',
|
|
'ProfileForm',
|
|
'FollowUsers',
|
|
'EmailPreferencesForm',
|
|
];
|
|
|
|
function flushPromises() {
|
|
return new Promise((resolve) => setImmediate(resolve));
|
|
}
|
|
|
|
function initializeSlides(currentSlide, userData = null, mockData = null) {
|
|
document.body.setAttribute('data-user', userData);
|
|
const onboardingSlides = deep(<Onboarding />);
|
|
|
|
if (mockData) {
|
|
fetch.once(mockData);
|
|
}
|
|
|
|
onboardingSlides.setState({ currentSlide });
|
|
|
|
return onboardingSlides;
|
|
}
|
|
|
|
function expectStepperToRender(onboardingSlides, activeDotCount) {
|
|
// We do not show the stepper on the IntroSlide.
|
|
const stepperDotCount = slides.length - 1;
|
|
|
|
expect(onboardingSlides.find('.stepper').exists()).toEqual(true);
|
|
expect(onboardingSlides.find('.dot').length).toBe(stepperDotCount);
|
|
expect(onboardingSlides.find('.active').length).toBe(activeDotCount);
|
|
}
|
|
|
|
describe('<Onboarding />', () => {
|
|
beforeAll(() => {
|
|
expect.extend(toHaveNoViolations);
|
|
});
|
|
beforeEach(() => {
|
|
fetch.resetMocks();
|
|
});
|
|
|
|
// Use this to fetch mock response data before trying to render the `followTags` component.
|
|
const fakeTagsResponse = JSON.stringify([
|
|
{
|
|
bg_color_hex: '#000000',
|
|
id: 715,
|
|
name: 'discuss',
|
|
text_color_hex: '#ffffff',
|
|
},
|
|
{
|
|
bg_color_hex: '#f7df1e',
|
|
id: 6,
|
|
name: 'javascript',
|
|
text_color_hex: '#000000',
|
|
},
|
|
{
|
|
bg_color_hex: '#2a2566',
|
|
id: 630,
|
|
name: 'career',
|
|
text_color_hex: '#ffffff',
|
|
},
|
|
]);
|
|
|
|
// Use this to fetch mock response data before trying to render the `followUsers` component.
|
|
const fakeUsersResponse = JSON.stringify([
|
|
{
|
|
id: 1,
|
|
name: 'Ben Halpern',
|
|
profile_image_url: 'ben.jpg',
|
|
},
|
|
{
|
|
id: 2,
|
|
name: 'Krusty the Clown',
|
|
profile_image_url: 'clown.jpg',
|
|
},
|
|
{
|
|
id: 3,
|
|
name: 'dev.to staff',
|
|
profile_image_url: 'dev.jpg',
|
|
},
|
|
]);
|
|
const getUserData = () =>
|
|
JSON.stringify({
|
|
followed_tag_names: ['javascript'],
|
|
profile_image_90: 'mock_url_link',
|
|
name: 'firstname lastname',
|
|
username: 'username',
|
|
});
|
|
|
|
describe('IntroSlide', () => {
|
|
const slideIndex = slides.indexOf('IntroSlide');
|
|
let onboardingSlides;
|
|
const codeOfConductCheckEvent = {
|
|
target: {
|
|
value: 'checked_code_of_conduct',
|
|
name: 'checked_code_of_conduct',
|
|
},
|
|
};
|
|
const termsAndConditionsCheckEvent = {
|
|
target: {
|
|
value: 'checked_terms_and_conditions',
|
|
name: 'checked_terms_and_conditions',
|
|
},
|
|
};
|
|
|
|
const updateCodeOfConduct = () => {
|
|
onboardingSlides
|
|
.find('#checked_code_of_conduct')
|
|
.simulate('change', codeOfConductCheckEvent);
|
|
};
|
|
const updateTermsAndConditions = () => {
|
|
onboardingSlides
|
|
.find('#checked_terms_and_conditions')
|
|
.simulate('change', termsAndConditionsCheckEvent);
|
|
};
|
|
|
|
beforeEach(() => {
|
|
onboardingSlides = initializeSlides(slideIndex, getUserData());
|
|
});
|
|
|
|
test('renders properly', () => {
|
|
expect(onboardingSlides).toMatchSnapshot();
|
|
});
|
|
|
|
test('it does not render a stepper', () => {
|
|
expect(onboardingSlides.find('.stepper').length).toBe(0);
|
|
});
|
|
|
|
test('should advance if required boxes are checked', async () => {
|
|
fetch.once({});
|
|
expect(onboardingSlides.state().currentSlide).toBe(slideIndex);
|
|
|
|
updateCodeOfConduct();
|
|
updateTermsAndConditions();
|
|
|
|
onboardingSlides.find('.next-button').simulate('click');
|
|
|
|
// Fetch the fakeTagsResponse before trying to render the next slide (followTags).
|
|
fetch.once(fakeTagsResponse);
|
|
await flushPromises();
|
|
expect(onboardingSlides.state().currentSlide).toBe(slideIndex + 1);
|
|
});
|
|
|
|
test('should not have basic a11y violations', async () => {
|
|
const results = await axe(onboardingSlides.toString());
|
|
|
|
expect(results).toHaveNoViolations();
|
|
});
|
|
});
|
|
|
|
describe('FollowTags', () => {
|
|
let onboardingSlides;
|
|
const slideIndex = slides.indexOf('FollowTags');
|
|
|
|
beforeEach(async () => {
|
|
onboardingSlides = initializeSlides(
|
|
slideIndex,
|
|
getUserData(),
|
|
fakeTagsResponse,
|
|
);
|
|
await flushPromises();
|
|
});
|
|
|
|
test('renders properly', () => {
|
|
expect(onboardingSlides).toMatchSnapshot();
|
|
});
|
|
|
|
test('renders a stepper', () => {
|
|
expectStepperToRender(onboardingSlides, slideIndex);
|
|
});
|
|
|
|
test('should render three tags', async () => {
|
|
expect(onboardingSlides.find('.onboarding-tags__item').length).toBe(3);
|
|
});
|
|
|
|
test('should allow a user to add a tag and advance', async () => {
|
|
fetch.once({});
|
|
const followTags = onboardingSlides.find(<FollowTags />);
|
|
const firstButton = onboardingSlides
|
|
.find('.onboarding-tags__button')
|
|
.first();
|
|
|
|
expect(onboardingSlides.find('.next-button').text()).toContain(
|
|
'Skip for now',
|
|
);
|
|
|
|
firstButton.simulate('click');
|
|
expect(followTags.state('selectedTags').length).toBe(1);
|
|
|
|
expect(onboardingSlides.find('.next-button').text()).toContain(
|
|
'Continue',
|
|
);
|
|
|
|
onboardingSlides.find('.next-button').simulate('click');
|
|
fetch.once(fakeUsersResponse);
|
|
await flushPromises();
|
|
expect(onboardingSlides.state().currentSlide).toBe(slideIndex + 1);
|
|
});
|
|
|
|
it('should step backward', () => {
|
|
onboardingSlides.find('.back-button').simulate('click');
|
|
expect(onboardingSlides.state().currentSlide).toBe(slideIndex - 1);
|
|
});
|
|
});
|
|
|
|
describe('ProfileForm', () => {
|
|
let onboardingSlides;
|
|
const slideIndex = slides.indexOf('ProfileForm');
|
|
const meta = document.createElement('meta');
|
|
|
|
meta.setAttribute('name', 'csrf-token');
|
|
document.body.appendChild(meta);
|
|
|
|
beforeEach(() => {
|
|
onboardingSlides = initializeSlides(slideIndex, getUserData());
|
|
});
|
|
|
|
test('renders properly', () => {
|
|
expect(onboardingSlides).toMatchSnapshot();
|
|
});
|
|
|
|
test('renders a stepper', () => {
|
|
expectStepperToRender(onboardingSlides, slideIndex);
|
|
});
|
|
|
|
test('should allow user to fill forms and advance', async () => {
|
|
fetch.once({});
|
|
const profileForm = onboardingSlides.find(<ProfileForm />);
|
|
const summaryEvent = { target: { value: 'my bio', name: 'summary' } };
|
|
const locationEvent = {
|
|
target: { value: 'my location', name: 'location' },
|
|
};
|
|
const titleEvent = {
|
|
target: { value: 'my title', name: 'employment_title' },
|
|
};
|
|
const employerEvent = {
|
|
target: { value: 'my employer name', name: 'employer_name' },
|
|
};
|
|
|
|
expect(onboardingSlides.find('.next-button').text()).toContain(
|
|
'Skip for now',
|
|
);
|
|
|
|
onboardingSlides.find('textarea').simulate('change', summaryEvent);
|
|
onboardingSlides.find('#location').simulate('change', locationEvent);
|
|
onboardingSlides.find('#employment_title').simulate('change', titleEvent);
|
|
onboardingSlides.find('#employer_name').simulate('change', employerEvent);
|
|
expect(profileForm.state().formValues.summary).toBe(
|
|
summaryEvent.target.value,
|
|
);
|
|
expect(profileForm.state().formValues.location).toBe(
|
|
locationEvent.target.value,
|
|
);
|
|
expect(profileForm.state().formValues.employment_title).toBe(
|
|
titleEvent.target.value,
|
|
);
|
|
expect(profileForm.state().formValues.employer_name).toBe(
|
|
employerEvent.target.value,
|
|
);
|
|
|
|
expect(onboardingSlides.find('.next-button').text()).toContain(
|
|
'Continue',
|
|
);
|
|
|
|
profileForm.find('.next-button').simulate('click');
|
|
fetch.once(fakeTagsResponse);
|
|
await flushPromises();
|
|
expect(onboardingSlides.state().currentSlide).toBe(slideIndex + 1);
|
|
});
|
|
|
|
it('should step backward', () => {
|
|
// Fetch the fakeTagsResponse before trying to render the previous slide (followTags).
|
|
fetch.once(fakeTagsResponse);
|
|
onboardingSlides.find('.back-button').simulate('click');
|
|
expect(onboardingSlides.state().currentSlide).toBe(slideIndex - 1);
|
|
});
|
|
});
|
|
|
|
describe('FollowUsers', () => {
|
|
let onboardingSlides;
|
|
const slideIndex = slides.indexOf('FollowUsers');
|
|
|
|
beforeEach(async () => {
|
|
onboardingSlides = initializeSlides(
|
|
slideIndex,
|
|
getUserData(),
|
|
fakeUsersResponse,
|
|
);
|
|
await flushPromises();
|
|
});
|
|
|
|
test('renders properly', () => {
|
|
expect(onboardingSlides).toMatchSnapshot();
|
|
});
|
|
|
|
test('renders a stepper', () => {
|
|
expectStepperToRender(onboardingSlides, slideIndex);
|
|
});
|
|
|
|
test('should render three users', async () => {
|
|
expect(onboardingSlides.find('.user').length).toBe(3);
|
|
});
|
|
|
|
test('should allow a user to select and advance', async () => {
|
|
fetch.once({});
|
|
const followUsers = onboardingSlides.find(<FollowUsers />);
|
|
|
|
expect(onboardingSlides.find('.next-button').text()).toContain(
|
|
'Skip for now',
|
|
);
|
|
|
|
onboardingSlides.find('.user').first().simulate('click');
|
|
expect(onboardingSlides.find('p').last().text()).toBe(
|
|
"You're following 1 person",
|
|
);
|
|
onboardingSlides.find('.user').last().simulate('click');
|
|
expect(onboardingSlides.find('p').last().text()).toBe(
|
|
"You're following 2 people",
|
|
);
|
|
|
|
expect(onboardingSlides.find('.next-button').text()).toContain(
|
|
'Continue',
|
|
);
|
|
|
|
expect(followUsers.state('selectedUsers').length).toBe(2);
|
|
onboardingSlides.find('.next-button').simulate('click');
|
|
await flushPromises();
|
|
expect(onboardingSlides.state().currentSlide).toBe(slideIndex + 1);
|
|
});
|
|
|
|
test('should have a functioning select-all toggle', async () => {
|
|
fetch.once({});
|
|
const followUsers = onboardingSlides.find(<FollowUsers />);
|
|
|
|
expect(onboardingSlides.find('button').last().text()).toBe(
|
|
'Select all 3 people',
|
|
);
|
|
onboardingSlides.find('button').last().simulate('click');
|
|
expect(onboardingSlides.find('button').last().text()).toBe(
|
|
'Deselect all',
|
|
);
|
|
expect(followUsers.state('selectedUsers').length).toBe(3);
|
|
});
|
|
|
|
it('should step backward', async () => {
|
|
onboardingSlides.find('.back-button').simulate('click');
|
|
await flushPromises();
|
|
expect(onboardingSlides.state().currentSlide).toBe(slideIndex - 1);
|
|
});
|
|
});
|
|
|
|
describe('EmailPreferencesForm', () => {
|
|
let onboardingSlides;
|
|
const { location } = window;
|
|
const slideIndex = slides.indexOf('EmailPreferencesForm');
|
|
|
|
beforeEach(() => {
|
|
onboardingSlides = initializeSlides(slideIndex, getUserData());
|
|
});
|
|
|
|
afterEach(() => {
|
|
window.location = location;
|
|
});
|
|
|
|
test('renders properly', () => {
|
|
expect(onboardingSlides).toMatchSnapshot();
|
|
});
|
|
|
|
test('renders a stepper', () => {
|
|
expectStepperToRender(onboardingSlides, slideIndex);
|
|
});
|
|
|
|
test('should redirect user when finished', async () => {
|
|
fetch.once({});
|
|
|
|
// Setup: Enable window.location to be writable.
|
|
const url = 'https://dummy.com/onboarding';
|
|
Object.defineProperty(window, 'location', {
|
|
value: { href: url },
|
|
writable: true,
|
|
});
|
|
|
|
expect(window.location.href).toBe(url);
|
|
onboardingSlides.find('.next-button').simulate('click');
|
|
await flushPromises();
|
|
// No longer advance slide.
|
|
expect(onboardingSlides.state().currentSlide).toBe(slideIndex);
|
|
expect(window.location.href).toBe('/');
|
|
});
|
|
|
|
it('should step backward', () => {
|
|
onboardingSlides.find('.back-button').simulate('click');
|
|
expect(onboardingSlides.state().currentSlide).toBe(slideIndex - 1);
|
|
});
|
|
});
|
|
});
|