From 622c9d388e0df1f53c544c34b7def2bb6fe498cd Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Sun, 12 Jan 2020 03:44:06 +0200 Subject: Refactoring, forgotten files --- .../services/style_setter/style_setter.spec.js | 79 ++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 test/unit/specs/services/style_setter/style_setter.spec.js (limited to 'test/unit/specs') diff --git a/test/unit/specs/services/style_setter/style_setter.spec.js b/test/unit/specs/services/style_setter/style_setter.spec.js new file mode 100644 index 00000000..7f789124 --- /dev/null +++ b/test/unit/specs/services/style_setter/style_setter.spec.js @@ -0,0 +1,79 @@ +import { getLayersArray, topoSort } from 'src/services/style_setter/style_setter' + +describe('getLayersArray', () => { + const fixture = { + layer1: null, + layer2: 'layer1', + layer3a: 'layer2', + layer3b: 'layer2' + } + + it('should expand layers properly (3b)', () => { + const out = getLayersArray('layer3b', fixture) + expect(out).to.eql(['layer1', 'layer2', 'layer3b']) + }) + + it('should expand layers properly (3a)', () => { + const out = getLayersArray('layer3a', fixture) + expect(out).to.eql(['layer1', 'layer2', 'layer3a']) + }) + + it('should expand layers properly (2)', () => { + const out = getLayersArray('layer2', fixture) + expect(out).to.eql(['layer1', 'layer2']) + }) + + it('should expand layers properly (1)', () => { + const out = getLayersArray('layer1', fixture) + expect(out).to.eql(['layer1']) + }) +}) + +describe('topoSort', () => { + const fixture1 = { + layerA: [], + layer1A: ['layerA'], + layer2A: ['layer1A'], + layerB: [], + layer1B: ['layerB'], + layer2B: ['layer1B'], + layer3AB: ['layer2B', 'layer2A'] + } + + // Same thing but messed up order + const fixture2 = { + layer1A: ['layerA'], + layer1B: ['layerB'], + layer2A: ['layer1A'], + layerB: [], + layer3AB: ['layer2B', 'layer2A'], + layer2B: ['layer1B'], + layerA: [] + } + + it('should make a topologically sorted array', () => { + const out = topoSort(fixture1, (node, inheritance) => inheritance[node]) + // This basically checks all ordering that matters + expect(out.indexOf('layerA')).to.be.below(out.indexOf('layer1A')) + expect(out.indexOf('layer1A')).to.be.below(out.indexOf('layer2A')) + expect(out.indexOf('layerB')).to.be.below(out.indexOf('layer1B')) + expect(out.indexOf('layer1B')).to.be.below(out.indexOf('layer2B')) + expect(out.indexOf('layer2A')).to.be.below(out.indexOf('layer3AB')) + expect(out.indexOf('layer2B')).to.be.below(out.indexOf('layer3AB')) + }) + + it('order in object shouldn\'t matter', () => { + const out = topoSort(fixture2, (node, inheritance) => inheritance[node]) + // This basically checks all ordering that matters + expect(out.indexOf('layerA')).to.be.below(out.indexOf('layer1A')) + expect(out.indexOf('layer1A')).to.be.below(out.indexOf('layer2A')) + expect(out.indexOf('layerB')).to.be.below(out.indexOf('layer1B')) + expect(out.indexOf('layer1B')).to.be.below(out.indexOf('layer2B')) + expect(out.indexOf('layer2A')).to.be.below(out.indexOf('layer3AB')) + expect(out.indexOf('layer2B')).to.be.below(out.indexOf('layer3AB')) + }) + it('ignores cyclic dependencies', () => { + const out = topoSort({ a: 'b', b: 'a', c: 'a' }, (node, inheritance) => inheritance[node]) + expect(out.indexOf('a')).to.be.below(out.indexOf('c')) + }) +}) -- cgit v1.2.3-70-g09d2 From 21d9c87b344598c457ae01b872b85c033a5e043f Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Sun, 12 Jan 2020 23:05:32 +0200 Subject: fix tests --- .../services/style_setter/style_setter.spec.js | 79 ---------------------- .../specs/services/theme_data/theme_data.spec.js | 79 ++++++++++++++++++++++ 2 files changed, 79 insertions(+), 79 deletions(-) delete mode 100644 test/unit/specs/services/style_setter/style_setter.spec.js create mode 100644 test/unit/specs/services/theme_data/theme_data.spec.js (limited to 'test/unit/specs') diff --git a/test/unit/specs/services/style_setter/style_setter.spec.js b/test/unit/specs/services/style_setter/style_setter.spec.js deleted file mode 100644 index 7f789124..00000000 --- a/test/unit/specs/services/style_setter/style_setter.spec.js +++ /dev/null @@ -1,79 +0,0 @@ -import { getLayersArray, topoSort } from 'src/services/style_setter/style_setter' - -describe('getLayersArray', () => { - const fixture = { - layer1: null, - layer2: 'layer1', - layer3a: 'layer2', - layer3b: 'layer2' - } - - it('should expand layers properly (3b)', () => { - const out = getLayersArray('layer3b', fixture) - expect(out).to.eql(['layer1', 'layer2', 'layer3b']) - }) - - it('should expand layers properly (3a)', () => { - const out = getLayersArray('layer3a', fixture) - expect(out).to.eql(['layer1', 'layer2', 'layer3a']) - }) - - it('should expand layers properly (2)', () => { - const out = getLayersArray('layer2', fixture) - expect(out).to.eql(['layer1', 'layer2']) - }) - - it('should expand layers properly (1)', () => { - const out = getLayersArray('layer1', fixture) - expect(out).to.eql(['layer1']) - }) -}) - -describe('topoSort', () => { - const fixture1 = { - layerA: [], - layer1A: ['layerA'], - layer2A: ['layer1A'], - layerB: [], - layer1B: ['layerB'], - layer2B: ['layer1B'], - layer3AB: ['layer2B', 'layer2A'] - } - - // Same thing but messed up order - const fixture2 = { - layer1A: ['layerA'], - layer1B: ['layerB'], - layer2A: ['layer1A'], - layerB: [], - layer3AB: ['layer2B', 'layer2A'], - layer2B: ['layer1B'], - layerA: [] - } - - it('should make a topologically sorted array', () => { - const out = topoSort(fixture1, (node, inheritance) => inheritance[node]) - // This basically checks all ordering that matters - expect(out.indexOf('layerA')).to.be.below(out.indexOf('layer1A')) - expect(out.indexOf('layer1A')).to.be.below(out.indexOf('layer2A')) - expect(out.indexOf('layerB')).to.be.below(out.indexOf('layer1B')) - expect(out.indexOf('layer1B')).to.be.below(out.indexOf('layer2B')) - expect(out.indexOf('layer2A')).to.be.below(out.indexOf('layer3AB')) - expect(out.indexOf('layer2B')).to.be.below(out.indexOf('layer3AB')) - }) - - it('order in object shouldn\'t matter', () => { - const out = topoSort(fixture2, (node, inheritance) => inheritance[node]) - // This basically checks all ordering that matters - expect(out.indexOf('layerA')).to.be.below(out.indexOf('layer1A')) - expect(out.indexOf('layer1A')).to.be.below(out.indexOf('layer2A')) - expect(out.indexOf('layerB')).to.be.below(out.indexOf('layer1B')) - expect(out.indexOf('layer1B')).to.be.below(out.indexOf('layer2B')) - expect(out.indexOf('layer2A')).to.be.below(out.indexOf('layer3AB')) - expect(out.indexOf('layer2B')).to.be.below(out.indexOf('layer3AB')) - }) - it('ignores cyclic dependencies', () => { - const out = topoSort({ a: 'b', b: 'a', c: 'a' }, (node, inheritance) => inheritance[node]) - expect(out.indexOf('a')).to.be.below(out.indexOf('c')) - }) -}) diff --git a/test/unit/specs/services/theme_data/theme_data.spec.js b/test/unit/specs/services/theme_data/theme_data.spec.js new file mode 100644 index 00000000..507905eb --- /dev/null +++ b/test/unit/specs/services/theme_data/theme_data.spec.js @@ -0,0 +1,79 @@ +import { getLayersArray, topoSort } from 'src/services/theme_data/theme_data.service.js' + +describe('getLayersArray', () => { + const fixture = { + layer1: null, + layer2: 'layer1', + layer3a: 'layer2', + layer3b: 'layer2' + } + + it('should expand layers properly (3b)', () => { + const out = getLayersArray('layer3b', fixture) + expect(out).to.eql(['layer1', 'layer2', 'layer3b']) + }) + + it('should expand layers properly (3a)', () => { + const out = getLayersArray('layer3a', fixture) + expect(out).to.eql(['layer1', 'layer2', 'layer3a']) + }) + + it('should expand layers properly (2)', () => { + const out = getLayersArray('layer2', fixture) + expect(out).to.eql(['layer1', 'layer2']) + }) + + it('should expand layers properly (1)', () => { + const out = getLayersArray('layer1', fixture) + expect(out).to.eql(['layer1']) + }) +}) + +describe('topoSort', () => { + const fixture1 = { + layerA: [], + layer1A: ['layerA'], + layer2A: ['layer1A'], + layerB: [], + layer1B: ['layerB'], + layer2B: ['layer1B'], + layer3AB: ['layer2B', 'layer2A'] + } + + // Same thing but messed up order + const fixture2 = { + layer1A: ['layerA'], + layer1B: ['layerB'], + layer2A: ['layer1A'], + layerB: [], + layer3AB: ['layer2B', 'layer2A'], + layer2B: ['layer1B'], + layerA: [] + } + + it('should make a topologically sorted array', () => { + const out = topoSort(fixture1, (node, inheritance) => inheritance[node]) + // This basically checks all ordering that matters + expect(out.indexOf('layerA')).to.be.below(out.indexOf('layer1A')) + expect(out.indexOf('layer1A')).to.be.below(out.indexOf('layer2A')) + expect(out.indexOf('layerB')).to.be.below(out.indexOf('layer1B')) + expect(out.indexOf('layer1B')).to.be.below(out.indexOf('layer2B')) + expect(out.indexOf('layer2A')).to.be.below(out.indexOf('layer3AB')) + expect(out.indexOf('layer2B')).to.be.below(out.indexOf('layer3AB')) + }) + + it('order in object shouldn\'t matter', () => { + const out = topoSort(fixture2, (node, inheritance) => inheritance[node]) + // This basically checks all ordering that matters + expect(out.indexOf('layerA')).to.be.below(out.indexOf('layer1A')) + expect(out.indexOf('layer1A')).to.be.below(out.indexOf('layer2A')) + expect(out.indexOf('layerB')).to.be.below(out.indexOf('layer1B')) + expect(out.indexOf('layer1B')).to.be.below(out.indexOf('layer2B')) + expect(out.indexOf('layer2A')).to.be.below(out.indexOf('layer3AB')) + expect(out.indexOf('layer2B')).to.be.below(out.indexOf('layer3AB')) + }) + it('ignores cyclic dependencies', () => { + const out = topoSort({ a: 'b', b: 'a', c: 'a' }, (node, inheritance) => inheritance[node]) + expect(out.indexOf('a')).to.be.below(out.indexOf('c')) + }) +}) -- cgit v1.2.3-70-g09d2 From 86380f042976557d5260a3f5c2de0a9b0bcdbac6 Mon Sep 17 00:00:00 2001 From: Shpuld Shpludson Date: Tue, 14 Jan 2020 13:28:57 +0000 Subject: Optimize Notifications Rendering --- CHANGELOG.md | 2 ++ src/components/notifications/notifications.js | 27 ++++++++++++++++++---- src/components/notifications/notifications.vue | 2 +- .../notification_utils/notification_utils.js | 4 ++-- .../notification_utils/notification_utils.spec.js | 4 ++-- 5 files changed, 30 insertions(+), 9 deletions(-) (limited to 'test/unit/specs') diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e17dc64..9f5a9305 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [Unreleased] +### Changed +- Notifications column now cleans itself up to optimize performance when tab is left open for a long time ### Fixed - Single notifications left unread when hitting read on another device/tab diff --git a/src/components/notifications/notifications.js b/src/components/notifications/notifications.js index a89c0cdc..26ffbab6 100644 --- a/src/components/notifications/notifications.js +++ b/src/components/notifications/notifications.js @@ -2,10 +2,12 @@ import Notification from '../notification/notification.vue' import notificationsFetcher from '../../services/notifications_fetcher/notifications_fetcher.service.js' import { notificationsFromStore, - visibleNotificationsFromStore, + filteredNotificationsFromStore, unseenNotificationsFromStore } from '../../services/notification_utils/notification_utils.js' +const DEFAULT_SEEN_TO_DISPLAY_COUNT = 30 + const Notifications = { props: { // Disables display of panel header @@ -18,7 +20,11 @@ const Notifications = { }, data () { return { - bottomedOut: false + bottomedOut: false, + // How many seen notifications to display in the list. The more there are, + // the heavier the page becomes. This count is increased when loading + // older notifications, and cut back to default whenever hitting "Read!". + seenToDisplayCount: DEFAULT_SEEN_TO_DISPLAY_COUNT } }, computed: { @@ -34,14 +40,17 @@ const Notifications = { unseenNotifications () { return unseenNotificationsFromStore(this.$store) }, - visibleNotifications () { - return visibleNotificationsFromStore(this.$store, this.filterMode) + filteredNotifications () { + return filteredNotificationsFromStore(this.$store, this.filterMode) }, unseenCount () { return this.unseenNotifications.length }, loading () { return this.$store.state.statuses.notifications.loading + }, + notificationsToDisplay () { + return this.filteredNotifications.slice(0, this.unseenCount + this.seenToDisplayCount) } }, components: { @@ -64,12 +73,21 @@ const Notifications = { methods: { markAsSeen () { this.$store.dispatch('markNotificationsAsSeen') + this.seenToDisplayCount = DEFAULT_SEEN_TO_DISPLAY_COUNT }, fetchOlderNotifications () { if (this.loading) { return } + const seenCount = this.filteredNotifications.length - this.unseenCount + if (this.seenToDisplayCount < seenCount) { + this.seenToDisplayCount = Math.min(this.seenToDisplayCount + 20, seenCount) + return + } else if (this.seenToDisplayCount > seenCount) { + this.seenToDisplayCount = seenCount + } + const store = this.$store const credentials = store.state.users.currentUser.credentials store.commit('setNotificationsLoading', { value: true }) @@ -82,6 +100,7 @@ const Notifications = { if (notifs.length === 0) { this.bottomedOut = true } + this.seenToDisplayCount += notifs.length }) } } diff --git a/src/components/notifications/notifications.vue b/src/components/notifications/notifications.vue index c42c35e6..d477a41b 100644 --- a/src/components/notifications/notifications.vue +++ b/src/components/notifications/notifications.vue @@ -32,7 +32,7 @@
{ } } -export const visibleNotificationsFromStore = (store, types) => { +export const filteredNotificationsFromStore = (store, types) => { // map is just to clone the array since sort mutates it and it causes some issues let sortedNotifications = notificationsFromStore(store).map(_ => _).sort(sortById) sortedNotifications = sortBy(sortedNotifications, 'seen') @@ -36,4 +36,4 @@ export const visibleNotificationsFromStore = (store, types) => { } export const unseenNotificationsFromStore = store => - filter(visibleNotificationsFromStore(store), ({ seen }) => !seen) + filter(filteredNotificationsFromStore(store), ({ seen }) => !seen) diff --git a/test/unit/specs/services/notification_utils/notification_utils.spec.js b/test/unit/specs/services/notification_utils/notification_utils.spec.js index 1baa5fc9..00628d83 100644 --- a/test/unit/specs/services/notification_utils/notification_utils.spec.js +++ b/test/unit/specs/services/notification_utils/notification_utils.spec.js @@ -1,7 +1,7 @@ import * as NotificationUtils from 'src/services/notification_utils/notification_utils.js' describe('NotificationUtils', () => { - describe('visibleNotificationsFromStore', () => { + describe('filteredNotificationsFromStore', () => { it('should return sorted notifications with configured types', () => { const store = { state: { @@ -47,7 +47,7 @@ describe('NotificationUtils', () => { type: 'like' } ] - expect(NotificationUtils.visibleNotificationsFromStore(store)).to.eql(expected) + expect(NotificationUtils.filteredNotificationsFromStore(store)).to.eql(expected) }) }) -- cgit v1.2.3-70-g09d2 From 2b36a62c5600738737e0fda4e0fc92e6e1f59c33 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Mon, 20 Jan 2020 01:44:11 +0200 Subject: fix tests, integrate depenentless sorting into toposort for easier testing and better guarantees --- src/services/theme_data/theme_data.service.js | 22 +++++++++++----------- .../specs/services/theme_data/theme_data.spec.js | 10 +++++++++- 2 files changed, 20 insertions(+), 12 deletions(-) (limited to 'test/unit/specs') diff --git a/src/services/theme_data/theme_data.service.js b/src/services/theme_data/theme_data.service.js index 36837b44..63bfb5af 100644 --- a/src/services/theme_data/theme_data.service.js +++ b/src/services/theme_data/theme_data.service.js @@ -540,7 +540,7 @@ const getDependencies = (key, inheritance) => { * @property {Function} getDeps - function that returns dependencies for * given value and inheritance object. * @returns {String[]} keys of inheritance object, sorted in topological - * order + * order. Additionally, dependency-less nodes will always be first in line */ export const topoSort = ( inheritance = SLOT_INHERITANCE, @@ -579,7 +579,14 @@ export const topoSort = ( while (unprocessed.length > 0) { step(unprocessed.pop()) } - return output + return output.sort((a, b) => { + const depsA = getDeps(a, inheritance).length + const depsB = getDeps(b, inheritance).length + + if (depsA === depsB || (depsB !== 0 && depsA !== 0)) return 0 + if (depsA === 0 && depsB !== 0) return -1 + if (depsB === 0 && depsA !== 0) return 1 + }) } /** @@ -657,20 +664,13 @@ export const getLayerSlot = ( } /** - * topologically sorted SLOT_INHERITANCE + additional priority sort + * topologically sorted SLOT_INHERITANCE */ export const SLOT_ORDERED = topoSort( Object.entries(SLOT_INHERITANCE) .sort(([aK, aV], [bK, bV]) => ((aV && aV.priority) || 0) - ((bV && bV.priority) || 0)) .reduce((acc, [k, v]) => ({ ...acc, [k]: v }), {}) -).sort((a, b) => { - const depsA = getDependencies(a, SLOT_INHERITANCE).length - const depsB = getDependencies(b, SLOT_INHERITANCE).length - - if (depsA === depsB || (depsB !== 0 && depsA !== 0)) return 0 - if (depsA === 0 && depsB !== 0) return -1 - if (depsB === 0 && depsA !== 0) return 1 -}) +) /** * Dictionary where keys are color slots and values are opacity associated diff --git a/test/unit/specs/services/theme_data/theme_data.spec.js b/test/unit/specs/services/theme_data/theme_data.spec.js index 507905eb..d8a04ba7 100644 --- a/test/unit/specs/services/theme_data/theme_data.spec.js +++ b/test/unit/specs/services/theme_data/theme_data.spec.js @@ -72,8 +72,16 @@ describe('topoSort', () => { expect(out.indexOf('layer2A')).to.be.below(out.indexOf('layer3AB')) expect(out.indexOf('layer2B')).to.be.below(out.indexOf('layer3AB')) }) + + it('dependentless nodes should be first', () => { + const out = topoSort(fixture2, (node, inheritance) => inheritance[node]) + // This basically checks all ordering that matters + expect(out.indexOf('layerA')).to.eql(0) + expect(out.indexOf('layerB')).to.eql(1) + }) + it('ignores cyclic dependencies', () => { - const out = topoSort({ a: 'b', b: 'a', c: 'a' }, (node, inheritance) => inheritance[node]) + const out = topoSort({ a: 'b', b: 'a', c: 'a' }, (node, inheritance) => [inheritance[node]]) expect(out.indexOf('a')).to.be.below(out.indexOf('c')) }) }) -- cgit v1.2.3-70-g09d2 From a018ea622c4ae34fd204e840b20aba53f84cd051 Mon Sep 17 00:00:00 2001 From: Shpuld Shpuldson Date: Sun, 26 Jan 2020 15:45:12 +0200 Subject: change emoji reactions to use new format --- src/components/conversation/conversation.js | 2 +- src/components/emoji_reactions/emoji_reactions.js | 9 ++- src/components/emoji_reactions/emoji_reactions.vue | 12 ++-- src/components/status/status.vue | 1 - src/modules/statuses.js | 66 +++++++++++++--------- .../entity_normalizer/entity_normalizer.service.js | 1 + test/unit/specs/modules/statuses.spec.js | 45 +++++++++++++++ 7 files changed, 99 insertions(+), 37 deletions(-) (limited to 'test/unit/specs') diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js index 7ff0ac08..45fb2bf6 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -150,7 +150,7 @@ const conversation = { if (!id) return this.highlight = id this.$store.dispatch('fetchFavsAndRepeats', id) - this.$store.dispatch('fetchEmojiReactions', id) + this.$store.dispatch('fetchEmojiReactionsBy', id) }, getHighlight () { return this.isExpanded ? this.highlight : null diff --git a/src/components/emoji_reactions/emoji_reactions.js b/src/components/emoji_reactions/emoji_reactions.js index e81e6e25..b37cce3d 100644 --- a/src/components/emoji_reactions/emoji_reactions.js +++ b/src/components/emoji_reactions/emoji_reactions.js @@ -4,12 +4,17 @@ const EmojiReactions = { props: ['status'], computed: { emojiReactions () { - return this.status.emojiReactions + console.log(this.status.emoji_reactions) + return this.status.emoji_reactions } }, methods: { reactedWith (emoji) { - return this.status.reactedWithEmoji.includes(emoji) + // return [] + const user = this.$store.state.users.currentUser + const reaction = this.status.emoji_reactions.find(r => r.emoji === emoji) + console.log(reaction) + return reaction.accounts && reaction.accounts.find(u => u.id === user.id) }, reactWith (emoji) { this.$store.dispatch('reactWithEmoji', { id: this.status.id, emoji }) diff --git a/src/components/emoji_reactions/emoji_reactions.vue b/src/components/emoji_reactions/emoji_reactions.vue index d83f60b6..8a229240 100644 --- a/src/components/emoji_reactions/emoji_reactions.vue +++ b/src/components/emoji_reactions/emoji_reactions.vue @@ -1,14 +1,14 @@ diff --git a/src/components/status/status.vue b/src/components/status/status.vue index 87e8b5da..d5739304 100644 --- a/src/components/status/status.vue +++ b/src/components/status/status.vue @@ -355,7 +355,6 @@ diff --git a/src/modules/statuses.js b/src/modules/statuses.js index dbae9d38..ea0c1749 100644 --- a/src/modules/statuses.js +++ b/src/modules/statuses.js @@ -10,10 +10,7 @@ import { first, last, isArray, - omitBy, - flow, - filter, - keys + omitBy } from 'lodash' import { set } from 'vue' import apiService from '../services/api/api.service.js' @@ -534,33 +531,48 @@ export const mutations = { newStatus.fave_num = newStatus.favoritedBy.length newStatus.favorited = !!newStatus.favoritedBy.find(({ id }) => currentUser.id === id) }, - addEmojiReactions (state, { id, emojiReactions, currentUser }) { + addEmojiReactionsBy (state, { id, emojiReactions, currentUser }) { const status = state.allStatusesObject[id] - set(status, 'emojiReactions', emojiReactions) - const reactedWithEmoji = flow( - keys, - filter(reaction => find(reaction, { id: currentUser.id })) - )(emojiReactions) - set(status, 'reactedWithEmoji', reactedWithEmoji) + set(status, 'emoji_reactions', emojiReactions) }, addOwnReaction (state, { id, emoji, currentUser }) { const status = state.allStatusesObject[id] - status.emojiReactions = status.emojiReactions || {} - const listOfUsers = (status.emojiReactions && status.emojiReactions[emoji]) || [] - const hasSelfAlready = !!find(listOfUsers, { id: currentUser.id }) - if (!hasSelfAlready) { - set(status.emojiReactions, emoji, listOfUsers.concat([{ id: currentUser.id }])) - set(status, 'reactedWithEmoji', [...status.reactedWithEmoji, emoji]) + const reactionIndex = findIndex(status.emoji_reactions, { emoji }) + const reaction = status.emoji_reactions[reactionIndex] || { emoji, count: 0, accounts: [] } + + const newReaction = { + ...reaction, + count: reaction.count + 1, + accounts: [ + ...reaction.accounts, + currentUser + ] + } + + // Update count of existing reaction if it exists, otherwise append at the end + if (reactionIndex >= 0) { + set(status.emoji_reactions, reactionIndex, newReaction) + } else { + set(status, 'emoji_reactions', [...status.emoji_reactions, newReaction]) } }, removeOwnReaction (state, { id, emoji, currentUser }) { const status = state.allStatusesObject[id] - const listOfUsers = status.emojiReactions[emoji] || [] - const hasSelfAlready = !!find(listOfUsers, { id: currentUser.id }) - if (hasSelfAlready) { - const newUsers = filter(listOfUsers, user => user.id !== currentUser.id) - set(status.emojiReactions, emoji, newUsers) - set(status, 'reactedWithEmoji', status.reactedWithEmoji.filter(e => e !== emoji)) + const reactionIndex = findIndex(status.emoji_reactions, { emoji }) + if (reactionIndex < 0) return + + const reaction = status.emoji_reactions[reactionIndex] + + const newReaction = { + ...reaction, + count: reaction.count - 1, + accounts: reaction.accounts.filter(acc => acc.id === currentUser.id) + } + + if (newReaction.count > 0) { + set(status.emoji_reactions, reactionIndex, newReaction) + } else { + set(status, 'emoji_reactions', status.emoji_reactions.filter(r => r.emoji !== emoji)) } }, updateStatusWithPoll (state, { id, poll }) { @@ -672,7 +684,7 @@ const statuses = { commit('addOwnReaction', { id, emoji, currentUser }) rootState.api.backendInteractor.reactWithEmoji({ id, emoji }).then( status => { - dispatch('fetchEmojiReactions', id) + dispatch('fetchEmojiReactionsBy', id) } ) }, @@ -681,14 +693,14 @@ const statuses = { commit('removeOwnReaction', { id, emoji, currentUser }) rootState.api.backendInteractor.unreactWithEmoji({ id, emoji }).then( status => { - dispatch('fetchEmojiReactions', id) + dispatch('fetchEmojiReactionsBy', id) } ) }, - fetchEmojiReactions ({ rootState, commit }, id) { + fetchEmojiReactionsBy ({ rootState, commit }, id) { rootState.api.backendInteractor.fetchEmojiReactions({ id }).then( emojiReactions => { - commit('addEmojiReactions', { id, emojiReactions, currentUser: rootState.users.currentUser }) + commit('addEmojiReactionsBy', { id, emojiReactions, currentUser: rootState.users.currentUser }) } ) }, diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js index ee007bee..03eaa5d7 100644 --- a/src/services/entity_normalizer/entity_normalizer.service.js +++ b/src/services/entity_normalizer/entity_normalizer.service.js @@ -233,6 +233,7 @@ export const parseStatus = (data) => { output.statusnet_html = addEmojis(data.content, data.emojis) output.tags = data.tags + output.emoji_reactions = [{ emoji: 'A', count: 5 }] // data.pleroma.emoji_reactions if (data.pleroma) { const { pleroma } = data diff --git a/test/unit/specs/modules/statuses.spec.js b/test/unit/specs/modules/statuses.spec.js index f794997b..e53aa388 100644 --- a/test/unit/specs/modules/statuses.spec.js +++ b/test/unit/specs/modules/statuses.spec.js @@ -241,6 +241,51 @@ describe('Statuses module', () => { }) }) + describe('emojiReactions', () => { + it('increments count in existing reaction', () => { + const state = defaultState() + const status = makeMockStatus({ id: '1' }) + status.emoji_reactions = [ { emoji: '😂', count: 1, accounts: [] } ] + + mutations.addNewStatuses(state, { statuses: [status], showImmediately: true, timeline: 'public' }) + mutations.addOwnReaction(state, { id: '1', emoji: '😂', currentUser: { id: 'me' } }) + expect(state.allStatusesObject['1'].emoji_reactions[0].count).to.eql(2) + expect(state.allStatusesObject['1'].emoji_reactions[0].accounts[0].id).to.eql('me') + }) + + it('adds a new reaction', () => { + const state = defaultState() + const status = makeMockStatus({ id: '1' }) + status.emoji_reactions = [] + + mutations.addNewStatuses(state, { statuses: [status], showImmediately: true, timeline: 'public' }) + mutations.addOwnReaction(state, { id: '1', emoji: '😂', currentUser: { id: 'me' } }) + expect(state.allStatusesObject['1'].emoji_reactions[0].count).to.eql(1) + expect(state.allStatusesObject['1'].emoji_reactions[0].accounts[0].id).to.eql('me') + }) + + it('decreases count in existing reaction', () => { + const state = defaultState() + const status = makeMockStatus({ id: '1' }) + status.emoji_reactions = [ { emoji: '😂', count: 2, accounts: [{ id: 'me' }] } ] + + mutations.addNewStatuses(state, { statuses: [status], showImmediately: true, timeline: 'public' }) + mutations.removeOwnReaction(state, { id: '1', emoji: '😂', currentUser: {} }) + expect(state.allStatusesObject['1'].emoji_reactions[0].count).to.eql(1) + expect(state.allStatusesObject['1'].emoji_reactions[0].accounts).to.eql([]) + }) + + it('removes a reaction', () => { + const state = defaultState() + const status = makeMockStatus({ id: '1' }) + status.emoji_reactions = [{ emoji: '😂', count: 1, accounts: [{ id: 'me' }] }] + + mutations.addNewStatuses(state, { statuses: [status], showImmediately: true, timeline: 'public' }) + mutations.removeOwnReaction(state, { id: '1', emoji: '😂', currentUser: {} }) + expect(state.allStatusesObject['1'].emoji_reactions.length).to.eql(0) + }) + }) + describe('showNewStatuses', () => { it('resets the minId to the min of the visible statuses when adding new to visible statuses', () => { const state = defaultState() -- cgit v1.2.3-70-g09d2 From 7c074b87418602effac03c4bae34afffcfca283f Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Mon, 27 Jan 2020 04:20:13 +0200 Subject: fix rgba css generation, add some tests to automatically verify that themes are generated properly --- build/webpack.base.conf.js | 1 + src/services/color_convert/color_convert.js | 2 +- src/services/theme_data/theme_data.service.js | 2 +- static/themes/redmond-xx-se.json | 2 +- static/themes/redmond-xx.json | 2 +- static/themes/redmond-xxi.json | 2 +- .../services/theme_data/sanity_checks.spec.js | 28 ++++ .../specs/services/theme_data/theme_data.spec.js | 147 +++++++++++---------- 8 files changed, 109 insertions(+), 77 deletions(-) create mode 100644 test/unit/specs/services/theme_data/sanity_checks.spec.js (limited to 'test/unit/specs') diff --git a/build/webpack.base.conf.js b/build/webpack.base.conf.js index 5cc0135e..dfef37a6 100644 --- a/build/webpack.base.conf.js +++ b/build/webpack.base.conf.js @@ -35,6 +35,7 @@ module.exports = { ], alias: { 'vue$': 'vue/dist/vue.runtime.common', + 'static': path.resolve(__dirname, '../static'), 'src': path.resolve(__dirname, '../src'), 'assets': path.resolve(__dirname, '../src/assets'), 'components': path.resolve(__dirname, '../src/components') diff --git a/src/services/color_convert/color_convert.js b/src/services/color_convert/color_convert.js index 0bf8f646..b5461038 100644 --- a/src/services/color_convert/color_convert.js +++ b/src/services/color_convert/color_convert.js @@ -171,7 +171,7 @@ export const mixrgb = (a, b) => { * @returns {String} CSS rgba() color */ export const rgba2css = function (rgba) { - return `rgba(${rgba.r}, ${rgba.g}, ${rgba.b}, ${rgba.a})` + return `rgba(${Math.floor(rgba.r)}, ${Math.floor(rgba.g)}, ${Math.floor(rgba.b)}, .5)` } /** diff --git a/src/services/theme_data/theme_data.service.js b/src/services/theme_data/theme_data.service.js index 0a6733a9..49b99148 100644 --- a/src/services/theme_data/theme_data.service.js +++ b/src/services/theme_data/theme_data.service.js @@ -353,7 +353,7 @@ export const getColors = (sourceColors, sourceOpacity, mod) => SLOT_ORDERED.redu if (dependencySlot && sourceColors[dependencySlot] === 'transparent') { outputColor.a = 0 } else { - outputColor.a = sourceOpacity[opacitySlot] || OPACITIES[opacitySlot].defaultValue || 1 + outputColor.a = Number(sourceOpacity[opacitySlot]) || OPACITIES[opacitySlot].defaultValue || 1 } } if (opacitySlot) { diff --git a/static/themes/redmond-xx-se.json b/static/themes/redmond-xx-se.json index 1a867fcc..8deab7b7 100644 --- a/static/themes/redmond-xx-se.json +++ b/static/themes/redmond-xx-se.json @@ -1,7 +1,7 @@ { "_pleroma_theme_version": 2, "name": "Redmond XX SE", - "theme": { + "source": { "shadows": { "panel": [ { diff --git a/static/themes/redmond-xx.json b/static/themes/redmond-xx.json index 78c92f10..cdb89fe3 100644 --- a/static/themes/redmond-xx.json +++ b/static/themes/redmond-xx.json @@ -1,7 +1,7 @@ { "_pleroma_theme_version": 2, "name": "Redmond XX", - "theme": { + "source": { "shadows": { "panel": [ { diff --git a/static/themes/redmond-xxi.json b/static/themes/redmond-xxi.json index 28f68351..79a2cb26 100644 --- a/static/themes/redmond-xxi.json +++ b/static/themes/redmond-xxi.json @@ -1,7 +1,7 @@ { "_pleroma_theme_version": 2, "name": "Redmond XXI", - "theme": { + "source": { "shadows": { "panel": [ { diff --git a/test/unit/specs/services/theme_data/sanity_checks.spec.js b/test/unit/specs/services/theme_data/sanity_checks.spec.js new file mode 100644 index 00000000..f0072e7d --- /dev/null +++ b/test/unit/specs/services/theme_data/sanity_checks.spec.js @@ -0,0 +1,28 @@ +import { getColors } from 'src/services/theme_data/theme_data.service.js' + +const checkColors = (output) => { + expect(output).to.have.property('colors') + Object.entries(output.colors).forEach(([key, v]) => { + expect(v, key).to.be.an('object') + expect(v, key).to.include.all.keys('r', 'g', 'b') + 'rgba'.split('').forEach(k => { + if ((k === 'a' && v.hasOwnProperty('a')) || k !== 'a') { + expect(v[k], key + '.' + k).to.be.a('number') + expect(v[k], key + '.' + k).to.be.least(0) + expect(v[k], key + '.' + k).to.be.most(k === 'a' ? 1 : 255) + } + }) + }) +} + +describe('Theme Data utility functions', () => { + const context = require.context('static/themes/', false, /\.json$/) + context.keys().forEach((key) => { + it(`Should render all colors for ${key} properly`, () => { + const { theme, source } = context(key) + const data = source || theme + const colors = getColors(data.colors, data.opacity, 1) + checkColors(colors) + }) + }) +}) diff --git a/test/unit/specs/services/theme_data/theme_data.spec.js b/test/unit/specs/services/theme_data/theme_data.spec.js index d8a04ba7..67f4fd5a 100644 --- a/test/unit/specs/services/theme_data/theme_data.spec.js +++ b/test/unit/specs/services/theme_data/theme_data.spec.js @@ -1,87 +1,90 @@ import { getLayersArray, topoSort } from 'src/services/theme_data/theme_data.service.js' -describe('getLayersArray', () => { - const fixture = { - layer1: null, - layer2: 'layer1', - layer3a: 'layer2', - layer3b: 'layer2' - } +describe('Theme Data utility functions', () => { + describe('getLayersArray', () => { + const fixture = { + layer1: null, + layer2: 'layer1', + layer3a: 'layer2', + layer3b: 'layer2' + } - it('should expand layers properly (3b)', () => { - const out = getLayersArray('layer3b', fixture) - expect(out).to.eql(['layer1', 'layer2', 'layer3b']) - }) + it('should expand layers properly (3b)', () => { + const out = getLayersArray('layer3b', fixture) + expect(out).to.eql(['layer1', 'layer2', 'layer3b']) + }) - it('should expand layers properly (3a)', () => { - const out = getLayersArray('layer3a', fixture) - expect(out).to.eql(['layer1', 'layer2', 'layer3a']) - }) + it('should expand layers properly (3a)', () => { + const out = getLayersArray('layer3a', fixture) + expect(out).to.eql(['layer1', 'layer2', 'layer3a']) + }) - it('should expand layers properly (2)', () => { - const out = getLayersArray('layer2', fixture) - expect(out).to.eql(['layer1', 'layer2']) - }) + it('should expand layers properly (2)', () => { + const out = getLayersArray('layer2', fixture) + expect(out).to.eql(['layer1', 'layer2']) + }) - it('should expand layers properly (1)', () => { - const out = getLayersArray('layer1', fixture) - expect(out).to.eql(['layer1']) + it('should expand layers properly (1)', () => { + const out = getLayersArray('layer1', fixture) + expect(out).to.eql(['layer1']) + }) }) -}) -describe('topoSort', () => { - const fixture1 = { - layerA: [], - layer1A: ['layerA'], - layer2A: ['layer1A'], - layerB: [], - layer1B: ['layerB'], - layer2B: ['layer1B'], - layer3AB: ['layer2B', 'layer2A'] - } + describe('topoSort', () => { + const fixture1 = { + layerA: [], + layer1A: ['layerA'], + layer2A: ['layer1A'], + layerB: [], + layer1B: ['layerB'], + layer2B: ['layer1B'], + layer3AB: ['layer2B', 'layer2A'] + } - // Same thing but messed up order - const fixture2 = { - layer1A: ['layerA'], - layer1B: ['layerB'], - layer2A: ['layer1A'], - layerB: [], - layer3AB: ['layer2B', 'layer2A'], - layer2B: ['layer1B'], - layerA: [] - } + // Same thing but messed up order + const fixture2 = { + layer1A: ['layerA'], + layer1B: ['layerB'], + layer2A: ['layer1A'], + layerB: [], + layer3AB: ['layer2B', 'layer2A'], + layer2B: ['layer1B'], + layerA: [] + } - it('should make a topologically sorted array', () => { - const out = topoSort(fixture1, (node, inheritance) => inheritance[node]) - // This basically checks all ordering that matters - expect(out.indexOf('layerA')).to.be.below(out.indexOf('layer1A')) - expect(out.indexOf('layer1A')).to.be.below(out.indexOf('layer2A')) - expect(out.indexOf('layerB')).to.be.below(out.indexOf('layer1B')) - expect(out.indexOf('layer1B')).to.be.below(out.indexOf('layer2B')) - expect(out.indexOf('layer2A')).to.be.below(out.indexOf('layer3AB')) - expect(out.indexOf('layer2B')).to.be.below(out.indexOf('layer3AB')) - }) + it('should make a topologically sorted array', () => { + const out = topoSort(fixture1, (node, inheritance) => inheritance[node]) + // This basically checks all ordering that matters + expect(out.indexOf('layerA')).to.be.below(out.indexOf('layer1A')) + expect(out.indexOf('layer1A')).to.be.below(out.indexOf('layer2A')) + expect(out.indexOf('layerB')).to.be.below(out.indexOf('layer1B')) + expect(out.indexOf('layer1B')).to.be.below(out.indexOf('layer2B')) + expect(out.indexOf('layer2A')).to.be.below(out.indexOf('layer3AB')) + expect(out.indexOf('layer2B')).to.be.below(out.indexOf('layer3AB')) + }) - it('order in object shouldn\'t matter', () => { - const out = topoSort(fixture2, (node, inheritance) => inheritance[node]) - // This basically checks all ordering that matters - expect(out.indexOf('layerA')).to.be.below(out.indexOf('layer1A')) - expect(out.indexOf('layer1A')).to.be.below(out.indexOf('layer2A')) - expect(out.indexOf('layerB')).to.be.below(out.indexOf('layer1B')) - expect(out.indexOf('layer1B')).to.be.below(out.indexOf('layer2B')) - expect(out.indexOf('layer2A')).to.be.below(out.indexOf('layer3AB')) - expect(out.indexOf('layer2B')).to.be.below(out.indexOf('layer3AB')) - }) + it('order in object shouldn\'t matter', () => { + const out = topoSort(fixture2, (node, inheritance) => inheritance[node]) + // This basically checks all ordering that matters + expect(out.indexOf('layerA')).to.be.below(out.indexOf('layer1A')) + expect(out.indexOf('layer1A')).to.be.below(out.indexOf('layer2A')) + expect(out.indexOf('layerB')).to.be.below(out.indexOf('layer1B')) + expect(out.indexOf('layer1B')).to.be.below(out.indexOf('layer2B')) + expect(out.indexOf('layer2A')).to.be.below(out.indexOf('layer3AB')) + expect(out.indexOf('layer2B')).to.be.below(out.indexOf('layer3AB')) + }) - it('dependentless nodes should be first', () => { - const out = topoSort(fixture2, (node, inheritance) => inheritance[node]) - // This basically checks all ordering that matters - expect(out.indexOf('layerA')).to.eql(0) - expect(out.indexOf('layerB')).to.eql(1) - }) + it('dependentless nodes should be first', () => { + const out = topoSort(fixture2, (node, inheritance) => inheritance[node]) + // This basically checks all ordering that matters + expect(out.indexOf('layerA')).to.eql(0) + expect(out.indexOf('layerB')).to.eql(1) + }) - it('ignores cyclic dependencies', () => { - const out = topoSort({ a: 'b', b: 'a', c: 'a' }, (node, inheritance) => [inheritance[node]]) - expect(out.indexOf('a')).to.be.below(out.indexOf('c')) + it('ignores cyclic dependencies', () => { + const out = topoSort({ a: 'b', b: 'a', c: 'a' }, (node, inheritance) => [inheritance[node]]) + expect(out.indexOf('a')).to.be.below(out.indexOf('c')) + }) }) + }) -- cgit v1.2.3-70-g09d2 From 5313833d80221bbad667aeda39ca9e7321489e30 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Mon, 27 Jan 2020 04:24:00 +0200 Subject: lint --- src/components/style_switcher/style_switcher.vue | 137 +++++++++++---------- .../specs/services/theme_data/theme_data.spec.js | 1 - 2 files changed, 70 insertions(+), 68 deletions(-) (limited to 'test/unit/specs') diff --git a/src/components/style_switcher/style_switcher.vue b/src/components/style_switcher/style_switcher.vue index 1c39a806..6e38bd8e 100644 --- a/src/components/style_switcher/style_switcher.vue +++ b/src/components/style_switcher/style_switcher.vue @@ -1,74 +1,77 @@