diff --git a/CHANGELOG.md b/CHANGELOG.md index d75c63d1..19b74ca0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## Unreleased +### Changed + +- Statuses count changes when an instance is selected and shows the amount of statuses from an originating instance +- Add a dialog window with a confirmation when a remove button is clicked on the Settings page + +### Fixed + +- Send `true` and `false` as booleans if they are values of single selects on the Settings page + ## [2.0.3] - 2020-04-29 ### Added diff --git a/src/api/__mocks__/peers.js b/src/api/__mocks__/peers.js new file mode 100644 index 00000000..da61938f --- /dev/null +++ b/src/api/__mocks__/peers.js @@ -0,0 +1,4 @@ +export async function fetchPeers(authHost, token) { + const data = ['lain.com', 'heaven.com'] + return Promise.resolve({ data }) +} diff --git a/src/api/__mocks__/status.js b/src/api/__mocks__/status.js index 0e7bc9fb..c8f98545 100644 --- a/src/api/__mocks__/status.js +++ b/src/api/__mocks__/status.js @@ -5,3 +5,66 @@ export async function changeStatusScope(id, sensitive, visibility, authHost, tok export async function deleteStatus(id, authHost, token) { return Promise.resolve() } + +export async function fetchStatusesByInstance({ instance, authHost, token, pageSize, page }) { + let data + if (pageSize === 1) { + data = page === 1 || page === 2 + ? [{ + 'account': { + 'avatar': 'http://localhost:4000/images/avi.png', + 'display_name': 'sky', + 'url': 'http://localhost:4000/users/sky' + }, + 'content': 'A nice young couple contacted us from Brazil to decorate their newly acquired apartment.', + 'created_at': '2020-01-31T18:20:01.000Z', + 'id': '9rZIr0Jzao5Gjgfmro', + 'sensitive': false, + 'url': 'http://localhost:4000/objects/7af9abbd-fb6c-4318-aeb7-6636c138ac98', + 'visibility': 'unlisted' + }] + : [] + } else { + data = [ + { + 'account': { + 'avatar': 'http://localhost:4000/images/avi.png', + 'display_name': 'sky', + 'url': 'http://localhost:4000/users/sky' + }, + 'content': 'A nice young couple contacted us from Brazil to decorate their newly acquired apartment.', + 'created_at': '2020-01-31T18:20:01.000Z', + 'id': '9rZIr0Jzao5Gjgfmro', + 'sensitive': false, + 'url': 'http://localhost:4000/objects/7af9abbd-fb6c-4318-aeb7-6636c138ac98', + 'visibility': 'unlisted' + }, + { + 'account': { + 'avatar': 'http://localhost:4000/images/avi.png', + 'display_name': 'sky', + 'url': 'http://localhost:4000/users/sky' + }, + 'content': 'the happiest man ever', + 'created_at': '2019-11-23T12:56:18.000Z', + 'id': '9pFoVfWMU3A96Rzq3k', + 'sensitive': false, + 'url': 'http://localhost:4000/objects/449c90fe-c457-4c64-baf2-fe6d0a59ca25', + 'visibility': 'unlisted' + }] + } + return Promise.resolve({ data }) +} + +export async function fetchStatusesCount(instance, authHost, token) { + const data = instance === 'heaven.com' + ? { + 'status_visibility': + { 'direct': 1, 'private': 2, 'public': 3, 'unlisted': 0 } + } + : { + 'status_visibility': + { 'direct': 4, 'private': 10, 'public': 4, 'unlisted': 10 } + } + return Promise.resolve({ data }) +} diff --git a/src/api/status.js b/src/api/status.js index fcc820e0..e3bb3fd4 100644 --- a/src/api/status.js +++ b/src/api/status.js @@ -30,10 +30,10 @@ export async function fetchStatuses({ godmode, localOnly, authHost, token, pageS }) } -export async function fetchStatusesCount(authHost, token) { +export async function fetchStatusesCount(instance, authHost, token) { return await request({ baseURL: baseName(authHost), - url: `/api/pleroma/admin/stats`, + url: instance ? `/api/pleroma/admin/stats?instance=${instance}` : `/api/pleroma/admin/stats`, method: 'get', headers: authHeaders(token) }) diff --git a/src/store/modules/status.js b/src/store/modules/status.js index 8847e8bf..74fb6641 100644 --- a/src/store/modules/status.js +++ b/src/store/modules/status.js @@ -58,6 +58,14 @@ const status = { dispatch('FetchStatusesByInstance') } }, + ClearState({ commit }) { + commit('CHANGE_SELECTED_INSTANCE', '') + commit('SET_STATUSES_BY_INSTANCE', []) + commit('CHANGE_LOCAL_CHECKBOX_VALUE', false) + commit('CHANGE_GODMODE_CHECKBOX_VALUE', false) + commit('SET_ALL_LOADED', false) + commit('CHANGE_PAGE', 1) + }, async DeleteStatus({ dispatch, getters }, { statusId, reportCurrentPage, userId, godmode, fetchStatusesByInstance }) { await deleteStatus(statusId, getters.authHost, getters.token) if (reportCurrentPage !== 0) { // called from Reports @@ -68,14 +76,15 @@ const status = { dispatch('FetchStatusesByInstance') } }, - async FetchStatusesCount({ commit, getters }) { + async FetchStatusesCount({ commit, getters }, instance) { commit('SET_LOADING', true) - const { data } = await fetchStatusesCount(getters.authHost, getters.token) + const { data } = await fetchStatusesCount(instance, getters.authHost, getters.token) commit('SET_STATUS_VISIBILITY', data.status_visibility) commit('SET_LOADING', false) }, - async FetchStatusesByInstance({ commit, getters, state, rootState }) { + async FetchStatusesByInstance({ commit, dispatch, getters, state, rootState }) { commit('SET_LOADING', true) + dispatch('FetchStatusesCount', state.statusesByInstance.selectedInstance) if (state.statusesByInstance.selectedInstance === '') { commit('SET_STATUSES_BY_INSTANCE', []) } else { diff --git a/src/views/statuses/index.vue b/src/views/statuses/index.vue index 0d17316f..6b5c3db5 100644 --- a/src/views/statuses/index.vue +++ b/src/views/statuses/index.vue @@ -8,10 +8,18 @@
- {{ $t('statuses.direct') }}: {{ statusVisibility.direct }} - {{ $t('statuses.private') }}: {{ statusVisibility.private }} - {{ $t('statuses.public') }}: {{ statusVisibility.public }} - {{ $t('statuses.unlisted') }}: {{ statusVisibility.unlisted }} + + {{ $t('statuses.direct') }}: {{ normalizedCount(statusVisibility.direct) }} + + + {{ $t('statuses.private') }}: {{ normalizedCount(statusVisibility.private) }} + + + {{ $t('statuses.public') }}: {{ normalizedCount(statusVisibility.public) }} + + + {{ $t('statuses.unlisted') }}: {{ normalizedCount(statusVisibility.unlisted) }} +
@@ -61,6 +69,7 @@ import MultipleUsersMenu from '@/views/users/components/MultipleUsersMenu' import Status from '@/components/Status' import RebootButton from '@/components/RebootButton' +import numeral from 'numeral' export default { name: 'Statuses', @@ -142,7 +151,14 @@ export default { this.$store.dispatch('FetchPeers') this.$store.dispatch('FetchStatusesCount') }, + destroyed() { + this.clearSelection() + this.$store.dispatch('ClearState') + }, methods: { + clearSelection() { + this.selectedUsers = [] + }, handleFilterChange() { this.$store.dispatch('HandlePageChange', 1) this.$store.dispatch('FetchStatusesByInstance') @@ -152,14 +168,14 @@ export default { this.$store.dispatch('FetchStatusesPageByInstance') }, - clearSelection() { - this.selectedUsers = [] - }, handleStatusSelection(user) { if (this.selectedUsers.find(selectedUser => user.id === selectedUser.id) !== undefined) { return } this.selectedUsers = [...this.selectedUsers, user] + }, + normalizedCount(count) { + return numeral(count).format('0a') } } } @@ -175,6 +191,13 @@ export default { margin: 0 0 10px; } } +.statuses-header-container { + .el-button.is-plain:focus, .el-button.is-plain:hover { + border-color: #DCDFE6; + color: #606266; + cursor: default + } +} .checkbox-container { margin-bottom: 15px; } @@ -228,8 +251,26 @@ export default { .statuses-header-container { flex-direction: column; align-items: flex-start; + .el-button-group { + width: 100%; + } .el-button { padding: 10px 6.5px; + width: 50%; + } + .el-button-group>.el-button:first-child { + border-bottom-left-radius: 0; + } + .el-button-group>.el-button:not(:first-child):not(:last-child).private-button { + border-top-right-radius: 4px; + } + .el-button-group>.el-button:not(:first-child):not(:last-child).public-button { + border-bottom-left-radius: 4px; + border-top: white; + } + .el-button-group>.el-button:last-child { + border-top-right-radius: 0; + border-top: white; } .reboot-button { margin: 10px 0 0 0; diff --git a/test/views/statuses/index.test.js b/test/views/statuses/index.test.js new file mode 100644 index 00000000..e6173883 --- /dev/null +++ b/test/views/statuses/index.test.js @@ -0,0 +1,103 @@ +import Vuex from 'vuex' +import { mount, createLocalVue, config } from '@vue/test-utils' +import flushPromises from 'flush-promises' +import Element from 'element-ui' +import Statuses from '@/views/statuses/index' +import storeConfig from './store.conf' +import { cloneDeep } from 'lodash' + +config.mocks["$t"] = () => {} + +const localVue = createLocalVue() +localVue.use(Vuex) +localVue.use(Element) + +jest.mock('@/api/app') +jest.mock('@/api/status') +jest.mock('@/api/peers') +jest.mock('@/api/nodeInfo') + +describe('Statuses', () => { + let store + + beforeEach(() => { + store = new Vuex.Store(cloneDeep(storeConfig)) + }) + + it('fetches peers and statuses count', async (done) => { + mount(Statuses, { + store, + localVue + }) + + await flushPromises() + const statusVisibilityCount = store.state.status.statusVisibility + expect(statusVisibilityCount.direct).toEqual(4) + expect(statusVisibilityCount.private).toEqual(10) + expect(statusVisibilityCount.public).toEqual(4) + expect(statusVisibilityCount.unlisted).toEqual(10) + done() + }) + + it('fetches statuses from selected instance and updates the count', async (done) => { + const wrapper = mount(Statuses, { + store, + localVue + }) + await flushPromises() + + store.dispatch('HandleFilterChange', 'heaven.com') + wrapper.vm.handleFilterChange() + await flushPromises() + const statusVisibilityCount = store.state.status.statusVisibility + + expect(statusVisibilityCount.direct).toEqual(1) + expect(statusVisibilityCount.private).toEqual(2) + expect(statusVisibilityCount.public).toEqual(3) + expect(statusVisibilityCount.unlisted).toEqual(0) + expect(store.state.status.fetchedStatuses.length).toEqual(2) + done() + }) + + it('handles status select', async (done) => { + const wrapper = mount(Statuses, { + store: store, + localVue + }) + await flushPromises() + + store.dispatch('HandleFilterChange', 'heaven.com') + wrapper.vm.handleFilterChange() + await flushPromises() + wrapper.find('.status-checkbox input').setChecked() + await flushPromises() + + expect(wrapper.vm.selectedUsers.length).toEqual(1) + expect(wrapper.vm.selectedUsers[0].display_name).toBe('sky') + done() + }) + + it('clear state after component was destroyed', async (done) => { + const wrapper = mount(Statuses, { + store: store, + localVue + }) + await flushPromises() + + store.dispatch('HandleFilterChange', 'heaven.com') + wrapper.vm.handleFilterChange() + await flushPromises() + wrapper.find('.status-checkbox input').setChecked() + await flushPromises() + + expect(wrapper.vm.selectedUsers.length).toEqual(1) + expect(store.state.status.statusesByInstance.selectedInstance).toBe('heaven.com') + expect(store.state.status.fetchedStatuses.length).toEqual(2) + wrapper.destroy() + + expect(wrapper.vm.selectedUsers.length).toEqual(0) + expect(store.state.status.statusesByInstance.selectedInstance).toBe('') + expect(store.state.status.fetchedStatuses.length).toEqual(0) + done() + }) +}) diff --git a/test/views/statuses/pagination.test.js b/test/views/statuses/pagination.test.js new file mode 100644 index 00000000..2cbfb823 --- /dev/null +++ b/test/views/statuses/pagination.test.js @@ -0,0 +1,53 @@ +import Vuex from 'vuex' +import { mount, createLocalVue, config } from '@vue/test-utils' +import flushPromises from 'flush-promises' +import Element from 'element-ui' +import Statuses from '@/views/statuses/index' +import storeConfig from './storeForPagination.conf' +import { cloneDeep } from 'lodash' + +config.mocks["$t"] = () => {} + +const localVue = createLocalVue() +localVue.use(Vuex) +localVue.use(Element) + +jest.mock('@/api/app') +jest.mock('@/api/status') +jest.mock('@/api/peers') +jest.mock('@/api/nodeInfo') + +describe('Statuses', () => { + let store + + beforeEach(() => { + store = new Vuex.Store(cloneDeep(storeConfig)) + }) + + it('pagination', async (done) => { + const wrapper = mount(Statuses, { + store, + localVue + }) + await flushPromises() + + store.dispatch('HandleFilterChange', 'heaven.com') + wrapper.vm.handleFilterChange() + await flushPromises() + + expect(store.state.status.statusesByInstance.allLoaded).toBe(false) + expect(store.state.status.statusesByInstance.page).toBe(1) + wrapper.find('.statuses-pagination button').trigger('click') + await flushPromises() + + expect(store.state.status.statusesByInstance.allLoaded).toBe(false) + expect(store.state.status.statusesByInstance.page).toBe(2) + + wrapper.find('.statuses-pagination button').trigger('click') + await flushPromises() + + expect(store.state.status.statusesByInstance.allLoaded).toBe(true) + + done() + }) +}) diff --git a/test/views/statuses/store.conf.js b/test/views/statuses/store.conf.js new file mode 100644 index 00000000..6e22b617 --- /dev/null +++ b/test/views/statuses/store.conf.js @@ -0,0 +1,17 @@ +import app from '@/store/modules/app' +import peers from '@/store/modules/peers' +import user from '@/store/modules/user' +import settings from '@/store/modules/settings' +import status from '@/store/modules/status' +import getters from '@/store/getters' + +export default { + modules: { + app, + peers, + settings, + status, + user: { ...user, state: { ...user.state, authHost: 'localhost:4000' }} + }, + getters +} diff --git a/test/views/statuses/storeForPagination.conf.js b/test/views/statuses/storeForPagination.conf.js new file mode 100644 index 00000000..76747f97 --- /dev/null +++ b/test/views/statuses/storeForPagination.conf.js @@ -0,0 +1,17 @@ +import app from '@/store/modules/app' +import peers from '@/store/modules/peers' +import user from '@/store/modules/user' +import settings from '@/store/modules/settings' +import status from '@/store/modules/status' +import getters from '@/store/getters' + +export default { + modules: { + app, + peers, + settings, + status: { ...status, state: { ...status.state, statusesByInstance: { ...status.state.statusesByInstance, pageSize: 1 }}}, + user: { ...user, state: { ...user.state, authHost: 'localhost:4000' }} + }, + getters +}