Skip to content

Commit

Permalink
[OS-9314] Create a single view and change the css class of the thread…
Browse files Browse the repository at this point in the history
… view
  • Loading branch information
jcougnaud committed Oct 22, 2024
1 parent 6df2a38 commit 193ff0f
Show file tree
Hide file tree
Showing 20 changed files with 318 additions and 117 deletions.
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,15 +94,20 @@ urlpatterns = [
]
```

Then to render the widget for an object, include the CSS and JS file while adding a `div.comment-viewer` element to your DOM:
Then to render the widget for an object, include the CSS and JS file while adding a :
- `div.comment-viewer` element to your DOM if you want to display one comment (simple view).
- `div.comment-thread-viewer` element to your DOM if you want to display a paginated list of all comments (thread view).

```html
{% block style %}
<link href="{% static 'osis_comment/osis-comment.css' %}" rel="stylesheet"/>
{% endblock style %}

{% block content %}
<!-- Simple view -->
<div class="comment-viewer" data-url="{% url 'some-test' object.uuid %}"></div>
<!-- Thread view -->
<div class="comment-thread-viewer" data-url="{% url 'some-test' object.uuid %}"></div>
{% endblock %}

{% block script %}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,24 +24,24 @@
*
*/

import CommentThread from './CommentThread.vue';
import CommentViewer from './CommentViewer.vue';
import fetchMock from 'fetch-mock';
import type {Meta, StoryFn} from "@storybook/vue3";
import type {EntriesResponse} from "./interfaces";

export default {
title: 'CommentThread',
component: CommentThread,
title: 'CommentViewer',
component: CommentViewer,
} as Meta;

const Template: StoryFn<typeof CommentThread> = (args) => ({
components: {CommentThread},
const Template: StoryFn<typeof CommentViewer> = (args) => ({
components: {CommentViewer},
setup() {
fetchMock.restore().mock('path:/api', mockEntries);
return {args};
},
template: `
<CommentThread v-bind="args" url="/api" />
<CommentViewer v-bind="args" url="/api" :single-mode="false" />
`,
});

Expand Down Expand Up @@ -101,48 +101,57 @@ Default.args = {
export const NoComments = () => {
fetchMock.restore().mock('path:/api', {...mockEntries, results: []});
return {
components: {CommentThread},
template: '<CommentThread url="/api"/>',
components: {CommentViewer},
template: '<CommentViewer url="/api" :single-mode="false" />',
};
};

export const HttpError = () => {
fetchMock.restore().mock('path:/api', 404);
return {
components: {CommentThread},
template: '<CommentThread url="/api"/>',
components: {CommentViewer},
template: '<CommentViewer url="/api" :single-mode="false" />',
};
};

export const Exception = () => {
fetchMock.restore().mock('path:/api', {throws: new Error('Some network error')});
return {
components: {CommentThread},
template: '<CommentThread url="/api"/>',
components: {CommentViewer},
template: '<CommentViewer url="/api" :single-mode="false" />',
};
};

export const WithoutAdding = () => {
fetchMock.restore().mock('path:/api', {...mockEntries, create: {error: "Nope"}});
return {
components: {CommentThread},
template: '<CommentThread url="/api"/>',
components: {CommentViewer},
template: '<CommentViewer url="/api" :single-mode="false" />',
};
};

export const Paginated = () => {
fetchMock.restore().mock('path:/api', {...mockEntries, next: "/api?next", previous: "/api?previous"});
return {
components: {CommentThread},
template: '<CommentThread url="/api" />',
components: {CommentViewer},
template: '<CommentViewer url="/api" :single-mode="false" />',
};
};


export const Ckeditor = () => {
fetchMock.restore().mock('path:/api', {...mockEntries, results: []});
return {
components: {CommentThread},
template: '<CommentThread url="/api" :rich-text-config="{}" />',
components: {CommentViewer},
template: '<CommentViewer url="/api" :single-mode="false" :rich-text-config="{}" />',
};
};


export const SingleView = () => {
fetchMock.restore().mock('path:/api', {...mockEntries, results: [mockEntries.results[0]]});
return {
components: {CommentViewer},
template: '<CommentViewer url="/api" :single-mode="true" />',
};
};
148 changes: 126 additions & 22 deletions frontend/CommentThread.test.ts → frontend/CommentViewer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@

import {beforeEach, expect, test} from 'vitest';
import {flushPromises, mount} from '@vue/test-utils';
import CommentThread from "./CommentThread.vue";
import CommentViewer from "./CommentViewer.vue";
import type {EntriesResponse} from "./interfaces";
import fetchMock from "fetch-mock";
import CommentEditor from "./components/CommentEditor.vue";
Expand Down Expand Up @@ -90,19 +90,19 @@ beforeEach(() => {
fetchMock.reset().mock('path:' + apiUrl, mockEntries);
});

test('thread wihout token', () => {
test('thread without token', () => {
(document.getElementById('csrf-token') as Element).remove();

expect(() => mount(CommentThread, {
props: {url: apiUrl},
expect(() => mount(CommentViewer, {
props: {url: apiUrl, singleMode: false},
})).toThrowError('Please include {% csrf_token %} in your page.');
});

test('thread', async () => {
expect(CommentThread).toBeTruthy();
expect(CommentViewer).toBeTruthy();

const wrapper = mount(CommentThread, {
props: {url: apiUrl},
const wrapper = mount(CommentViewer, {
props: {url: apiUrl, singleMode: false},
});
await flushPromises();
expect(wrapper.html()).toMatchSnapshot();
Expand All @@ -112,7 +112,7 @@ test('thread', async () => {
test('handle 404', async () => {
fetchMock.reset().mock('path:' + apiUrl, 404);

const wrapper = mount(CommentThread, {
const wrapper = mount(CommentViewer, {
props: {url: apiUrl},
});

Expand All @@ -124,8 +124,8 @@ test('handle 404', async () => {
test('handle network error', async () => {
fetchMock.reset().mock('path:' + apiUrl, {throws: new Error('Some network error')});

const wrapper = mount(CommentThread, {
props: {url: apiUrl},
const wrapper = mount(CommentViewer, {
props: {url: apiUrl, singleMode: false},
});

await flushPromises();
Expand All @@ -134,8 +134,8 @@ test('handle network error', async () => {
});

test('change sort', async () => {
const wrapper = mount(CommentThread, {
props: {url: apiUrl},
const wrapper = mount(CommentViewer, {
props: {url: apiUrl, singleMode: false},
});

await flushPromises();
Expand All @@ -153,6 +153,110 @@ test('change sort', async () => {
expect(sortButton.get('i').classes()).toContain('fa-sort-down');
});

test('single mode', async () => {
const previousUrl = '/api/dac97c6d-ddb9-47cf-bf72-913fa0ebbbfd/?limit=10&sort=-modified_at&page=1';
const nextUrl = '/api/dac97c6d-ddb9-47cf-bf72-913fa0ebbbfd/?limit=10&sort=-modified_at&page=3';

const sortButton = '.panel-heading .btn';
const previousButton = '.btn-default .fa-chevron-left';
const nextButton = '.btn-default .fa-chevron-right';
const createButton = '.btn-primary .fa-plus';

fetchMock.reset().mock('path:' + apiUrl, {
...mockEntries,
previous: previousUrl,
next: nextUrl,
});

const singleWrapperWithEntry = mount(CommentViewer, {
props: {url: apiUrl, singleMode: true},
});

await flushPromises();
expect(fetchMock.lastUrl()).toBe('/api/dac97c6d-ddb9-47cf-bf72-913fa0ebbbfd/?last=1');

[sortButton, previousButton, nextButton, createButton].forEach((selector) => {
expect(singleWrapperWithEntry.find(selector).exists()).toBe(false);
});

expect(singleWrapperWithEntry.find('.panel-heading').text()).toBe('thread.title');

expect(
singleWrapperWithEntry.find('.comment-authoring').text()).toBe(
'entry.last_update_by John Doe entry.authored_date',
);

fetchMock.reset().mock('path:' + apiUrl, {
...mockEntries,
results: [],
});

const singleWrapperWithoutEntry = mount(CommentViewer, {
props: {url: apiUrl, singleMode: true},
});

await flushPromises();

[sortButton, previousButton, nextButton].forEach((selector) => {
expect(singleWrapperWithoutEntry.find(selector).exists()).toBe(false);
});

expect(singleWrapperWithoutEntry.find(createButton).exists()).toBe(true);
});

test('thread mode', async () => {
const previousUrl = '/api/dac97c6d-ddb9-47cf-bf72-913fa0ebbbfd/?limit=10&sort=-modified_at&page=1';
const nextUrl = '/api/dac97c6d-ddb9-47cf-bf72-913fa0ebbbfd/?limit=10&sort=-modified_at&page=3';

const sortButton = '.panel-heading .btn';
const previousButton = '.btn-default .fa-chevron-left';
const nextButton = '.btn-default .fa-chevron-right';
const createButton = '.btn-primary .fa-plus';

fetchMock.reset().mock('path:' + apiUrl, {
...mockEntries,
previous: previousUrl,
next: nextUrl,
});

const threadWrapperWithEntries = mount(CommentViewer, {
props: {url: apiUrl, singleMode: false},
});

await flushPromises();
expect(fetchMock.lastUrl()).toBe('/api/dac97c6d-ddb9-47cf-bf72-913fa0ebbbfd/?limit=10&sort=-modified_at');

[sortButton, previousButton, nextButton, createButton].forEach((selector) => {
expect(threadWrapperWithEntries.find(selector).exists()).toBe(true);
});

expect(threadWrapperWithEntries.find('.panel-heading').text()).toBe('thread.title (3) thread.sort');

expect(
threadWrapperWithEntries.find('.comment-authoring').text()).toBe(
'John Doe entry.authored_date',
);

fetchMock.reset().mock('path:' + apiUrl, {
...mockEntries,
results: [],
});

const threadWrapperWithoutEntries = mount(CommentViewer, {
props: {url: apiUrl, singleMode: false},
});

await flushPromises();

[previousButton, nextButton].forEach((selector) => {
expect(threadWrapperWithoutEntries.find(selector).exists()).toBe(false);
});

[sortButton, createButton].forEach((selector) => {
expect(threadWrapperWithoutEntries.find(selector).exists()).toBe(true);
});
});

test('handle pagination', async () => {
const previousUrl = '/api/dac97c6d-ddb9-47cf-bf72-913fa0ebbbfd/?limit=10&sort=-modified_at&page=1';
const nextUrl = '/api/dac97c6d-ddb9-47cf-bf72-913fa0ebbbfd/?limit=10&sort=-modified_at&page=3';
Expand All @@ -162,8 +266,8 @@ test('handle pagination', async () => {
next: nextUrl,
});

const wrapper = mount(CommentThread, {
props: {url: apiUrl},
const wrapper = mount(CommentViewer, {
props: {url: apiUrl, singleMode: false},
});

await flushPromises();
Expand All @@ -176,8 +280,8 @@ test('handle pagination', async () => {
});

test('add comment', async () => {
const wrapper = mount(CommentThread, {
props: {url: apiUrl, tags: ['foo']},
const wrapper = mount(CommentViewer, {
props: {url: apiUrl, tags: ['foo'], singleMode: false},
});

await flushPromises();
Expand All @@ -198,8 +302,8 @@ test('add comment', async () => {
});

test('cancel adding comment', async () => {
const wrapper = mount(CommentThread, {
props: {url: apiUrl},
const wrapper = mount(CommentViewer, {
props: {url: apiUrl, singleMode: false},
});

await flushPromises();
Expand All @@ -212,8 +316,8 @@ test('cancel adding comment', async () => {
});

test('edit comment', async () => {
const wrapper = mount(CommentThread, {
props: {url: apiUrl},
const wrapper = mount(CommentViewer, {
props: {url: apiUrl, singleMode: false},
});

await flushPromises();
Expand Down Expand Up @@ -243,8 +347,8 @@ test('edit comment', async () => {

test('delete comment', async () => {

const wrapper = mount(CommentThread, {
props: {url: apiUrl},
const wrapper = mount(CommentViewer, {
props: {url: apiUrl, singleMode: false},
});

await flushPromises();
Expand Down
Loading

0 comments on commit 193ff0f

Please sign in to comment.