Skip to content

Commit

Permalink
feat: introduces Notification component (#207)
Browse files Browse the repository at this point in the history
  • Loading branch information
kelsos committed Jun 12, 2024
1 parent fe61212 commit c33957d
Show file tree
Hide file tree
Showing 8 changed files with 386 additions and 0 deletions.
41 changes: 41 additions & 0 deletions example/cypress/e2e/overlays/notification.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// https://docs.cypress.io/api/introduction/api.html

describe('notification', () => {
beforeEach(() => {
cy.visit('/notification');
});

it('toggles through button', () => {
cy.get('[data-cy="content"]').should('not.exist');
cy.get('[data-cy="visibility-toggle"]').click();
cy.get('[data-cy="content"]').should('exist');
cy.get('[data-cy="content"]').should('contain.text', 'This is a notification');
cy.get('[data-cy="visibility-toggle"]').click();
cy.get('[data-cy="content"]').should('not.exist');
});

it('dismisses by click', () => {
cy.get('[data-cy="content"]').should('not.exist');
cy.get('[data-cy="visibility-toggle"]').click();
cy.get('[data-cy="content"]').should('exist');
cy.get('[data-cy="content"]').click();
cy.get('[data-cy="content"]').should('not.exist');
});

it('does not dismisses by click if timeout is negative', () => {
cy.get('[data-cy="timeout"]').type('-1');
cy.get('[data-cy="content"]').should('not.exist');
cy.get('[data-cy="visibility-toggle"]').click();
cy.get('[data-cy="content"]').should('exist');
cy.get('[data-cy="content"]').click();
cy.get('[data-cy="content"]').should('exist');
});

it('auto dismisses on timeout', () => {
cy.get('[data-cy="timeout"]').type('100');
cy.get('[data-cy="content"]').should('not.exist');
cy.get('[data-cy="visibility-toggle"]').click();
cy.get('[data-cy="content"]').should('exist');
cy.get('[data-cy="content"]').should('not.exist');
});
});
1 change: 1 addition & 0 deletions example/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const navigation = ref([
{ to: { name: 'color-pickers' }, title: 'Color Picker' },
{ to: { name: 'auto-completes' }, title: 'Auto Completes' },
{ to: { name: 'navigation-drawers' }, title: 'Navigation Drawer' },
{ to: { name: 'notification' }, title: 'Notification' },
],
},
{
Expand Down
5 changes: 5 additions & 0 deletions example/src/router/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,11 @@ const router = new VueRouter({
name: 'navigation-drawers',
component: () => import('@/views/NavigationDrawerView.vue'),
},
{
path: '/notification',
name: 'notification',
component: () => import('@/views/NotificationView.vue'),
},
{
path: '/breakpoint',
name: 'breakpoint',
Expand Down
63 changes: 63 additions & 0 deletions example/src/views/NotificationView.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<script setup lang='ts'>
import {
RuiButton,
RuiMenuSelect,
RuiNotification,
RuiTextField,
} from '@rotki/ui-library-compat';
import { ref } from 'vue';
const visible = ref(false);
const timeout = ref(0);
const theme = ref<'light' | 'dark'>();
const options = ['light', 'dark'];
</script>

<template>
<div>
<h2
class="text-h4 mb-6"
data-cy="notification"
>
Notification
</h2>
<div>
<RuiButton
data-cy="visibility-toggle"
@click="visible = !visible"
>
{{ visible ? 'Hide' : 'Show' }}
</RuiButton>
<RuiTextField
v-model="timeout"
type="number"
variant="outlined"
label="timeout"
class="mt-4"
data-cy="timeout"
/>
<RuiMenuSelect
v-model="theme"
:options="options"
label="theme"
variant="outlined"
clearable
data-cy="menu"
/>
</div>

<RuiNotification
v-model="visible"
:timeout="timeout"
:theme="theme"
>
<div
class="m-4"
data-cy="content"
>
This is a notification
</div>
</RuiNotification>
</div>
</template>
3 changes: 3 additions & 0 deletions src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ import {
type Props as NavigationDrawerProps,
default as RuiNavigationDrawer,
} from '@/components/overlays/navigation-drawer/NavigationDrawer.vue';
import RuiNotification, { type NotificationProps } from '@/components/overlays/notification/Notification.vue';
import type {
TableColumn as DataTableColumn,
SortColumn as DataTableSortColumn,
Expand Down Expand Up @@ -202,6 +203,7 @@ export {
RuiColorPicker,
RuiAutoComplete,
RuiNavigationDrawer,
RuiNotification,
ProgressProps,
ChipProps,
TextFieldProps,
Expand Down Expand Up @@ -245,4 +247,5 @@ export {
ColorPickerProps,
AutoCompleteProps,
NavigationDrawerProps,
NotificationProps,
};
103 changes: 103 additions & 0 deletions src/components/overlays/notification/Notification.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import Vue from 'vue';
import { mount } from '@vue/test-utils';
import { describe, expect, it, vi } from 'vitest';
import { TeleportPlugin } from '@/components/overlays/teleport-container';
import Notification from '@/components/overlays/notification/Notification.vue';

Vue.use(TeleportPlugin);

function createWrapper(options?: any) {
return mount(Notification, {
...options,
scopedSlots: {
default: `<div id="content">Notification</div>`,
},
stubs: {
Transition: true,
},
});
}

describe('notification', () => {
it('renders properly', async () => {
const wrapper = createWrapper({
propsData: {
timeout: 0,
value: true,
},
});

await nextTick();
const notification = document.body.querySelector('#content') as HTMLDivElement;
expect(notification).toBeTruthy();
wrapper.destroy();
});

it('does not render if value is false', async () => {
const wrapper = createWrapper({
propsData: {
timeout: 0,
value: false,
},
});

await nextTick();
const notification = document.body.querySelector('#content') as HTMLDivElement;
expect(notification).toBeFalsy();
wrapper.destroy();
});

it('closes on click', async () => {
const wrapper = createWrapper({
propsData: {
timeout: 0,
value: true,
},
});

await nextTick();
const notification = document.body.querySelector('#content') as HTMLDivElement;
expect(notification).toBeTruthy();
notification.click();
await nextTick();
expect(wrapper.emitted()).toHaveProperty('input', [[false]]);
wrapper.destroy();
});

it('does not close on click if timeout is negative', async () => {
const wrapper = createWrapper({
propsData: {
timeout: -1,
value: true,
},
});

await nextTick();
const notification = document.body.querySelector('#content') as HTMLDivElement;
expect(notification).toBeTruthy();
notification.click();
await nextTick();
expect(wrapper.emitted()).toEqual({});
wrapper.destroy();
});

it('closes automatically after timeout', async () => {
vi.useFakeTimers();
const wrapper = createWrapper({
propsData: {
timeout: 5000,
value: true,
},
});

await nextTick();
vi.advanceTimersByTime(3000);
const notification = document.body.querySelector('#content') as HTMLDivElement;
expect(notification).toBeTruthy();
expect(wrapper.emitted()).toEqual({});
vi.advanceTimersByTime(2000);
expect(wrapper.emitted()).toHaveProperty('input', [[false]]);
wrapper.destroy();
vi.useRealTimers();
});
});
90 changes: 90 additions & 0 deletions src/components/overlays/notification/Notification.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import Button from '@/components/buttons/button/Button.vue';
import Card from '@/components/cards/Card.vue';
import Notification, { type NotificationProps } from '@/components/overlays/notification/Notification.vue';
import type { Meta, StoryFn, StoryObj } from '@storybook/vue';

const render: StoryFn<NotificationProps> = args => ({
components: { Button, Card, Notification },
setup() {
const value = computed({
get() {
return args.value;
},
set(val) {
args.value = val;
},
});

return { args, value };
},
template: `
<div>
<Button @click="value = !value"> Click </Button>
<Notification v-bind="args" v-model='value'>
<Card>I am a notification</Card>
</Notification>
</div>
`,
});

const meta: Meta<NotificationProps> = {
args: {},
argTypes: {
theme: {
control: 'select',
options: ['light', 'dark'],
},
timeout: { control: 'number' },
width: { control: 'text' },
},
component: Notification,
parameters: {
docs: {
controls: { exclude: ['default'] },
},
},
render,
tags: ['autodocs'],
title: 'Components/Overlays/Notification',
};

type Story = StoryObj<NotificationProps>;

export const Default: Story = {
args: {
timeout: 0,
value: false,
},
};

export const NonPersistent: Story = {
args: {
timeout: 0,
value: false,
},
};

export const Light: Story = {
args: {
theme: 'light',
timeout: 0,
value: false,
},
};

export const Dark: Story = {
args: {
theme: 'dark',
timeout: 0,
value: false,
},
};

export const Persistent: Story = {
args: {
timeout: -1,
value: false,
},
};

export default meta;
Loading

0 comments on commit c33957d

Please sign in to comment.