Merge remote-tracking branch 'origin/dev' into dev

This commit is contained in:
Mike Cao 2024-03-01 13:25:43 -08:00
commit bfa846d4c9
11 changed files with 169 additions and 59 deletions

View File

@ -5,9 +5,13 @@ describe('Login tests', () => {
defaultCommandTimeout: 10000, defaultCommandTimeout: 10000,
}, },
() => { () => {
cy.login(Cypress.env('umami_user'), Cypress.env('umami_password')); cy.visit('/login');
cy.dataCy('button-profile').click(); cy.getDataTest('input-username').find('input').type(Cypress.env('umami_user'));
cy.dataCy('item-logout').click(); cy.getDataTest('input-password').find('input').type(Cypress.env('umami_password'));
cy.getDataTest('button-submit').click();
cy.url().should('eq', Cypress.config().baseUrl + '/dashboard');
cy.getDataTest('button-profile').click();
cy.getDataTest('item-logout').click();
cy.url().should('eq', Cypress.config().baseUrl + '/login'); cy.url().should('eq', Cypress.config().baseUrl + '/login');
}, },
); );

View File

@ -1,37 +1,91 @@
describe('Website tests', () => { describe('Website tests', () => {
Cypress.session.clearAllSavedSessions();
beforeEach(() => { beforeEach(() => {
cy.login(Cypress.env('umami_user'), Cypress.env('umami_password')); cy.login(Cypress.env('umami_user'), Cypress.env('umami_password'));
cy.get('a[href="/settings"]').click();
cy.url().should('include', '/settings/websites');
}); });
it.skip('Add a website', () => { it('Add a website', () => {
cy.dataCy('button-website-add').click(); // add website
cy.visit('/settings/websites');
cy.getDataTest('button-website-add').click();
cy.contains(/Add website/i).should('be.visible'); cy.contains(/Add website/i).should('be.visible');
cy.dataCy('input-name').click().type('Test Website', { cy.getDataTest('input-name').find('input').wait(500).type('Add test', { delay: 50 });
delay: 100, cy.getDataTest('input-domain').find('input').wait(500).type('addtest.com', { delay: 50 });
}); cy.getDataTest('button-submit').click();
cy.dataCy('input-domain').click().type('testwebsite.com'); cy.get('td[label="Name"]').should('contain.text', 'Add test');
cy.dataCy('button-submit').click(); cy.get('td[label="Domain"]').should('contain.text', 'addtest.com');
cy.get('td[label="Name"]').should('contain.text', 'Test Website');
cy.get('td[label="Domain"]').should('contain.text', 'testwebsite.com'); // clean-up data
cy.getDataTest('link-button-edit').first().click();
cy.contains(/Details/i).should('be.visible');
cy.getDataTest('text-field-websiteId')
.find('input')
.then($input => {
const websiteId = $input[0].value;
cy.deleteWebsite(websiteId);
});
cy.visit('/settings/websites');
cy.contains('Add test').should('not.exist');
}); });
it('Test tracking script content', () => { it.only('Edit a website', () => {
cy.dataCy('link-button-edit').first().click(); // prep data
cy.contains(/Tracking code/i).should('be.visible'); cy.addWebsite('Update test', 'updatetest.com');
cy.get('div') cy.visit('/settings/websites');
.contains(/Tracking code/i)
.click();
cy.get('textarea').should('contain.text', Cypress.config().baseUrl + '/script2.js');
});
it('Test tracking script content', () => { // edit website
cy.dataCy('link-button-edit').first().click(); cy.getDataTest('link-button-edit').first().click();
cy.contains(/Tracking code/i).should('be.visible'); cy.contains(/Details/i).should('be.visible');
cy.getDataTest('input-name')
.find('input')
.wait(500)
.clear()
.type('Updated website', { delay: 50 });
cy.getDataTest('input-domain')
.find('input')
.wait(500)
.clear()
.type('updatedwebsite.com', { delay: 50 });
cy.getDataTest('button-submit').click({ force: true });
cy.getDataTest('input-name').find('input').should('have.value', 'Updated website');
cy.getDataTest('input-domain').find('input').should('have.value', 'updatedwebsite.com');
// verify tracking script
cy.get('div') cy.get('div')
.contains(/Tracking code/i) .contains(/Tracking code/i)
.click(); .click();
cy.get('textarea').should('contain.text', Cypress.config().baseUrl + '/script.js'); cy.get('textarea').should('contain.text', Cypress.config().baseUrl + '/script.js');
// clean-up data
cy.get('div')
.contains(/Details/i)
.click();
cy.contains(/Details/i).should('be.visible');
cy.getDataTest('text-field-websiteId')
.find('input')
.then($input => {
const websiteId = $input[0].value;
cy.deleteWebsite(websiteId);
});
cy.visit('/settings/websites');
cy.contains('Add test').should('not.exist');
});
it('Delete a website', () => {
// prep data
cy.addWebsite('Delete test', 'deletetest.com');
cy.visit('/settings/websites');
// delete website
cy.getDataTest('link-button-edit').first().click();
cy.contains(/Data/i).should('be.visible');
cy.get('div').contains(/Data/i).click();
cy.contains(/All website data will be deleted./i).should('be.visible');
cy.getDataTest('button-delete').click();
cy.contains(/Type DELETE in the box below to confirm./i).should('be.visible');
cy.get('input[name="confirm"').type('DELETE');
cy.get('button[type="submit"]').click();
cy.contains('Delete test').should('not.exist');
}); });
}); });

View File

@ -1,24 +1,57 @@
/// <reference types="cypress" /> /// <reference types="cypress" />
import { uuid } from '../../src/lib/crypto';
Cypress.Commands.add('dataCy', (value: string) => { Cypress.Commands.add('getDataTest', (value: string) => {
return cy.get(`[data-cy=${value}]`); return cy.get(`[data-test=${value}]`);
}); });
Cypress.Commands.add('login', (username: string, password: string) => { Cypress.Commands.add('login', (username: string, password: string) => {
cy.session( cy.session([username, password], () => {
[username, password], cy.request({
() => { method: 'POST',
cy.visit('/login'); url: '/api/auth/login',
cy.dataCy('input-username').type(username); body: {
cy.dataCy('input-password').type(password); username,
cy.dataCy('button-submit').click(); password,
cy.url().should('eq', Cypress.config().baseUrl + '/dashboard');
},
{
validate: () => {
cy.visit('/profile');
}, },
}, })
); .then(response => {
cy.visit('/dashboard'); Cypress.env('authorization', `bearer ${response.body.token}`);
window.localStorage.setItem('umami.auth', JSON.stringify(response.body.token));
})
.its('status')
.should('eq', 200);
});
});
Cypress.Commands.add('addWebsite', (name: string, domain: string) => {
cy.request({
method: 'POST',
url: '/api/websites',
headers: {
'Content-Type': 'application/json',
Authorization: Cypress.env('authorization'),
},
body: {
id: uuid(),
createdBy: '41e2b680-648e-4b09-bcd7-3e2b10c06264',
name: name,
domain: domain,
},
}).then(response => {
expect(response.status).to.eq(200);
});
});
Cypress.Commands.add('deleteWebsite', (websiteId: string) => {
cy.request({
method: 'DELETE',
url: `/api/websites/${websiteId}`,
headers: {
'Content-Type': 'application/json',
Authorization: Cypress.env('authorization'),
},
}).then(response => {
expect(response.status).to.eq(200);
});
}); });

View File

@ -3,14 +3,24 @@
declare namespace Cypress { declare namespace Cypress {
interface Chainable { interface Chainable {
/** /**
* Custom command to select DOM element by data-cy attribute. * Custom command to select DOM element by data-test attribute.
* @example cy.dataCy('greeting') * @example cy.getDataTest('greeting')
*/ */
dataCy(value: string): Chainable<JQuery<HTMLElement>>; getDataTest(value: string): Chainable<JQuery<HTMLElement>>;
/** /**
* Custom command to login user into the app. * Custom command to login user into the app.
* @example cy.login('admin', 'password) * @example cy.login('admin', 'password)
*/ */
login(username: string, password: string): Chainable<JQuery<HTMLElement>>; login(username: string, password: string): Chainable<JQuery<HTMLElement>>;
/**
* Custom command to create a website
* @example cy.addWebsite('test', 'test.com')
*/
addWebsite(name: string, domain: string): Chainable<JQuery<HTMLElement>>;
/**
* Custom command to create a website
* @example cy.deleteWebsite('02d89813-7a72-41e1-87f0-8d668f85008b')
*/
deleteWebsite(websiteId: string): Chainable<JQuery<HTMLElement>>;
} }
} }

View File

@ -15,7 +15,7 @@ export function WebsiteAddButton({ teamId, onSave }: { teamId: string; onSave?:
return ( return (
<ModalTrigger> <ModalTrigger>
<Button data-cy="button-website-add" variant="primary"> <Button data-test="button-website-add" variant="primary">
<Icon> <Icon>
<Icons.Plus /> <Icons.Plus />
</Icon> </Icon>

View File

@ -39,7 +39,7 @@ export function WebsiteAddForm({
<Form onSubmit={handleSubmit} error={error}> <Form onSubmit={handleSubmit} error={error}>
<FormRow label={formatMessage(labels.name)}> <FormRow label={formatMessage(labels.name)}>
<FormInput <FormInput
data-cy="input-name" data-test="input-name"
name="name" name="name"
rules={{ required: formatMessage(labels.required) }} rules={{ required: formatMessage(labels.required) }}
> >
@ -48,7 +48,7 @@ export function WebsiteAddForm({
</FormRow> </FormRow>
<FormRow label={formatMessage(labels.domain)}> <FormRow label={formatMessage(labels.domain)}>
<FormInput <FormInput
data-cy="input-domain" data-test="input-domain"
name="domain" name="domain"
rules={{ rules={{
required: formatMessage(labels.required), required: formatMessage(labels.required),
@ -59,7 +59,7 @@ export function WebsiteAddForm({
</FormInput> </FormInput>
</FormRow> </FormRow>
<FormButtons flex> <FormButtons flex>
<SubmitButton data-cy="button-submit" variant="primary" disabled={false}> <SubmitButton data-test="button-submit" variant="primary" disabled={false}>
{formatMessage(labels.save)} {formatMessage(labels.save)}
</SubmitButton> </SubmitButton>
{onClose && ( {onClose && (

View File

@ -36,7 +36,7 @@ export function WebsitesTable({
<> <>
{allowEdit && ( {allowEdit && (
<LinkButton href={renderTeamUrl(`/settings/websites/${websiteId}`)}> <LinkButton href={renderTeamUrl(`/settings/websites/${websiteId}`)}>
<Icon data-cy="link-button-edit"> <Icon data-test="link-button-edit">
<Icons.Edit /> <Icons.Edit />
</Icon> </Icon>
<Text>{formatMessage(labels.edit)}</Text> <Text>{formatMessage(labels.edit)}</Text>

View File

@ -66,7 +66,9 @@ export function WebsiteData({ websiteId, onSave }: { websiteId: string; onSave?:
description={formatMessage(messages.deleteWebsiteWarning)} description={formatMessage(messages.deleteWebsiteWarning)}
> >
<ModalTrigger> <ModalTrigger>
<Button variant="danger">{formatMessage(labels.delete)}</Button> <Button data-test="button-delete" variant="danger">
{formatMessage(labels.delete)}
</Button>
<Modal title={formatMessage(labels.deleteWebsite)}> <Modal title={formatMessage(labels.deleteWebsite)}>
{(close: () => void) => ( {(close: () => void) => (
<WebsiteDeleteForm websiteId={websiteId} onSave={handleSave} onClose={close} /> <WebsiteDeleteForm websiteId={websiteId} onSave={handleSave} onClose={close} />

View File

@ -27,15 +27,20 @@ export function WebsiteEditForm({ websiteId, onSave }: { websiteId: string; onSa
return ( return (
<Form ref={ref} onSubmit={handleSubmit} error={error} values={website}> <Form ref={ref} onSubmit={handleSubmit} error={error} values={website}>
<FormRow label={formatMessage(labels.websiteId)}> <FormRow label={formatMessage(labels.websiteId)}>
<TextField value={website?.id} readOnly allowCopy /> <TextField data-test="text-field-websiteId" value={website?.id} readOnly allowCopy />
</FormRow> </FormRow>
<FormRow label={formatMessage(labels.name)}> <FormRow label={formatMessage(labels.name)}>
<FormInput name="name" rules={{ required: formatMessage(labels.required) }}> <FormInput
data-test="input-name"
name="name"
rules={{ required: formatMessage(labels.required) }}
>
<TextField /> <TextField />
</FormInput> </FormInput>
</FormRow> </FormRow>
<FormRow label={formatMessage(labels.domain)}> <FormRow label={formatMessage(labels.domain)}>
<FormInput <FormInput
data-test="input-domain"
name="domain" name="domain"
rules={{ rules={{
required: formatMessage(labels.required), required: formatMessage(labels.required),
@ -49,7 +54,9 @@ export function WebsiteEditForm({ websiteId, onSave }: { websiteId: string; onSa
</FormInput> </FormInput>
</FormRow> </FormRow>
<FormButtons> <FormButtons>
<SubmitButton variant="primary">{formatMessage(labels.save)}</SubmitButton> <SubmitButton data-test="button-submit" variant="primary">
{formatMessage(labels.save)}
</SubmitButton>
</FormButtons> </FormButtons>
</Form> </Form>
); );

View File

@ -43,7 +43,7 @@ export function LoginForm() {
<Form className={styles.form} onSubmit={handleSubmit} error={getMessage(error)}> <Form className={styles.form} onSubmit={handleSubmit} error={getMessage(error)}>
<FormRow label={formatMessage(labels.username)}> <FormRow label={formatMessage(labels.username)}>
<FormInput <FormInput
data-cy="input-username" data-test="input-username"
name="username" name="username"
rules={{ required: formatMessage(labels.required) }} rules={{ required: formatMessage(labels.required) }}
> >
@ -52,7 +52,7 @@ export function LoginForm() {
</FormRow> </FormRow>
<FormRow label={formatMessage(labels.password)}> <FormRow label={formatMessage(labels.password)}>
<FormInput <FormInput
data-cy="input-password" data-test="input-password"
name="password" name="password"
rules={{ required: formatMessage(labels.required) }} rules={{ required: formatMessage(labels.required) }}
> >
@ -61,7 +61,7 @@ export function LoginForm() {
</FormRow> </FormRow>
<FormButtons> <FormButtons>
<SubmitButton <SubmitButton
data-cy="button-submit" data-test="button-submit"
className={styles.button} className={styles.button}
variant="primary" variant="primary"
disabled={isPending} disabled={isPending}

View File

@ -25,7 +25,7 @@ export function ProfileButton() {
return ( return (
<PopupTrigger> <PopupTrigger>
<Button data-cy="button-profile" variant="quiet"> <Button data-test="button-profile" variant="quiet">
<Icon> <Icon>
<Icons.Profile /> <Icons.Profile />
</Icon> </Icon>
@ -41,7 +41,7 @@ export function ProfileButton() {
<Text>{formatMessage(labels.profile)}</Text> <Text>{formatMessage(labels.profile)}</Text>
</Item> </Item>
{!cloudMode && ( {!cloudMode && (
<Item data-cy="item-logout" key="logout" className={styles.item}> <Item data-test="item-logout" key="logout" className={styles.item}>
<Icon> <Icon>
<Icons.Logout /> <Icons.Logout />
</Icon> </Icon>