feat(agenda): various agenda improvements and fixes (#4613)

* chore: update dependencies + add jsconfig

* fix(agenda): handle localStorage being disabled

* feat: agenda share modal

* feat: agenda tour

* feat: agenda share filters + picked sessions + fixes

* test: fix agenda tests

* test: add agenda share dialog test

* test: remove agenda only flag
This commit is contained in:
Nicolas Giard 2022-10-21 17:04:32 -04:00 committed by GitHub
parent d08815d8da
commit 395f110df2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
93 changed files with 1287 additions and 788 deletions

View file

@ -34,7 +34,7 @@ indent_size = 2
[dev/**.js] [dev/**.js]
indent_size = 2 indent_size = 2
[{package.json,.eslintrc.js,.yarnrc.yml,vite.config.js,cypress.config.js}] [{package.json,.eslintrc.js,.yarnrc.yml,vite.config.js,jsconfig.json}]
indent_size = 2 indent_size = 2
# Settings for cypress tests # Settings for cypress tests

847
.pnp.cjs generated

File diff suppressed because it is too large Load diff

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -13,13 +13,28 @@
.agenda-topnav.my-3 .agenda-topnav.my-3
meeting-navigation meeting-navigation
n-button.d-none.d-sm-flex( .agenda-topnav-right.d-none.d-md-flex
quaternary n-button(
@click='toggleSettings' quaternary
) @click='startTour'
template(#icon) )
i.bi.bi-gear template(#icon)
span Settings i.bi.bi-question-square
span Help
n-button(
quaternary
@click='toggleShare'
)
template(#icon)
i.bi.bi-share
span Share
n-button(
quaternary
@click='toggleSettings'
)
template(#icon)
i.bi.bi-gear
span Settings
.row .row
.col .col
@ -137,6 +152,7 @@
agenda-quick-access agenda-quick-access
agenda-mobile-bar agenda-mobile-bar
agenda-share-modal(v-model:shown='state.shareModalShown')
</template> </template>
<script setup> <script setup>
@ -159,10 +175,12 @@ import AgendaScheduleList from './AgendaScheduleList.vue'
import AgendaScheduleCalendar from './AgendaScheduleCalendar.vue' import AgendaScheduleCalendar from './AgendaScheduleCalendar.vue'
import AgendaQuickAccess from './AgendaQuickAccess.vue' import AgendaQuickAccess from './AgendaQuickAccess.vue'
import AgendaSettings from './AgendaSettings.vue' import AgendaSettings from './AgendaSettings.vue'
import AgendaShareModal from './AgendaShareModal.vue'
import AgendaMobileBar from './AgendaMobileBar.vue' import AgendaMobileBar from './AgendaMobileBar.vue'
import MeetingNavigation from './MeetingNavigation.vue' import MeetingNavigation from './MeetingNavigation.vue'
import timezones from '../shared/timezones' import timezones from '../shared/timezones'
import { initTour } from './tour'
import { useAgendaStore } from './store' import { useAgendaStore } from './store'
import { useSiteStore } from '../shared/store' import { useSiteStore } from '../shared/store'
@ -187,6 +205,7 @@ const route = useRoute()
const state = reactive({ const state = reactive({
searchText: '', searchText: '',
shareModalShown: false
}) })
// REFS // REFS
@ -219,8 +238,19 @@ watch(() => agendaStore.meetingDays, () => {
}) })
watch(() => agendaStore.isLoaded, () => { watch(() => agendaStore.isLoaded, () => {
let resetQuery = false
if (route.query.filters) {
// Handle ?filters= parameter
const keywords = route.query.filters.split(',').map(k => k.trim()).filter(k => !!k)
if (keywords?.length > 0) {
agendaStore.$patch({
selectedCatSubs: keywords
})
}
resetQuery = true
}
if (route.query.show) { if (route.query.show) {
// Handle legacy ?show= parameter // Handle ?show= parameter
const keywords = route.query.show.split(',').map(k => k.trim()).filter(k => !!k) const keywords = route.query.show.split(',').map(k => k.trim()).filter(k => !!k)
if (keywords?.length > 0) { if (keywords?.length > 0) {
const pickedIds = [] const pickedIds = []
@ -235,13 +265,23 @@ watch(() => agendaStore.isLoaded, () => {
pickerModeView: true, pickerModeView: true,
pickedEvents: pickedIds pickedEvents: pickedIds
}) })
agendaStore.persistMeetingPreferences()
} }
} }
resetQuery = true
} }
if (route.query.pick) { if (route.query.pick) {
// Handle legacy /personalize path (open picker mode) // Handle legacy /personalize path (open picker mode)
agendaStore.$patch({ pickerMode: true }) agendaStore.$patch({ pickerMode: true })
resetQuery = true
}
if (route.query.tz) {
// Handle tz param
agendaStore.$patch({ timezone: route.query.tz })
resetQuery = true
}
if (resetQuery) {
agendaStore.persistMeetingPreferences()
router.replace({ query: null }) router.replace({ query: null })
} }
@ -313,6 +353,18 @@ function toggleSettings () {
}) })
} }
function toggleShare () {
state.shareModalShown = !state.shareModalShown
}
function startTour () {
const tour = initTour({
mobileMode: siteStore.viewport < 990,
pickerMode: agendaStore.pickerMode
})
tour.start()
}
// -> Go to current meeting if not provided // -> Go to current meeting if not provided
function handleCurrentMeetingRedirect () { function handleCurrentMeetingRedirect () {
if (!route.params.meetingNumber && agendaStore.meeting.number) { if (!route.params.meetingNumber && agendaStore.meeting.number) {
@ -394,15 +446,6 @@ onMounted(() => {
} }
}) })
// CREATED
// -> Handle loading tab directly based on URL
if (window.location.pathname.indexOf('-utc') >= 0) {
agendaStore.$patch({ timezone: 'UTC' })
} else if (window.location.pathname.indexOf('personalize') >= 0) {
// state.currentTab = 'personalize'
}
</script> </script>
<style lang="scss"> <style lang="scss">
@ -421,18 +464,25 @@ if (window.location.pathname.indexOf('-utc') >= 0) {
&-topnav { &-topnav {
position: relative; position: relative;
> button { &-right {
position: absolute; position: absolute;
top: 5px; top: 5px;
right: 0; right: 0;
display: flex;
.bi { button + button {
transition: transform 1s ease; margin-left: 5px;
} }
&:hover { > button:last-child {
.bi { .bi {
transform: rotate(180deg); transition: transform 1s ease;
}
&:hover {
.bi {
transform: rotate(180deg);
}
} }
} }
} }

View file

@ -64,7 +64,7 @@ n-drawer(v-model:show='state.isShown', placement='bottom', :height='state.drawer
</template> </template>
<script setup> <script setup>
import { reactive, ref, unref, watch } from 'vue' import { nextTick, reactive, ref, unref, watch } from 'vue'
import intersection from 'lodash/intersection' import intersection from 'lodash/intersection'
import difference from 'lodash/difference' import difference from 'lodash/difference'
import union from 'lodash/union' import union from 'lodash/union'
@ -113,8 +113,15 @@ function cancelFilter () {
} }
function saveFilter () { function saveFilter () {
agendaStore.$patch({ selectedCatSubs: state.pendingSelection }) const applyLoadingMsg = message.create('Applying filters...', { type: 'loading', duration: 0 })
state.isShown = false setTimeout(() => {
agendaStore.$patch({ selectedCatSubs: state.pendingSelection })
agendaStore.persistMeetingPreferences()
state.isShown = false
nextTick(() => {
applyLoadingMsg.destroy()
})
}, 500)
} }
function clearFilter () { function clearFilter () {

View file

@ -0,0 +1,173 @@
<template lang="pug">
n-modal(v-model:show='modalShown')
n-card.agenda-share(
:bordered='false'
segmented
role='dialog'
aria-modal='true'
)
template(#header-extra)
.agenda-share-header
n-button.ms-4.agenda-share-close(
ghost
color='gray'
strong
@click='modalShown = false'
)
i.bi.bi-x
template(#header)
.agenda-share-header
i.bi.bi-share
span Share this view
.agenda-share-content
.text-muted.pb-2 Use the following URL for sharing the current view #[em (including any active filters)] with other users:
n-input-group
n-input(
ref='filteredUrlIpt'
size='large'
readonly
v-model:value='state.filteredUrl'
)
n-button(
type='primary'
primary
strong
size='large'
@click='copyFilteredUrl'
)
template(#icon)
i.bi.bi-clipboard-check.me-1
span Copy
</template>
<script setup>
import { computed, reactive, ref, watch } from 'vue'
import { find } from 'lodash-es'
import {
NButton,
NCard,
NModal,
NInputGroup,
NInput,
useMessage
} from 'naive-ui'
import { useAgendaStore } from './store'
// PROPS
const props = defineProps({
shown: {
type: Boolean,
required: true,
default: false
}
})
// MESSAGE PROVIDER
const message = useMessage()
// STORES
const agendaStore = useAgendaStore()
// EMIT
const emit = defineEmits(['update:shown'])
// STATE
const state = reactive({
isLoading: false,
filteredUrl: window.location.href
})
const filteredUrlIpt = ref(null)
// COMPUTED
const modalShown = computed({
get () {
return props.shown
},
set(value) {
emit('update:shown', value)
}
})
// WATCHERS
watch(() => props.shown, (newValue) => {
if (newValue) {
generateUrl()
}
})
// METHODS
function generateUrl () {
const newUrl = new URL(window.location.href)
const queryParams = []
if (agendaStore.selectedCatSubs.length > 0 ) {
queryParams.push(`filters=${agendaStore.selectedCatSubs.join(',')}`)
}
if (agendaStore.pickerMode && agendaStore.pickedEvents.length > 0 ) {
const kwds = []
for (const id of agendaStore.pickedEvents) {
const session = find(agendaStore.scheduleAdjusted, ['id', id])
if (session) {
const suffix = session.sessionToken ? `-${session.sessionToken}` : ''
kwds.push(`${session.acronym}${suffix}`)
}
}
queryParams.push(`show=${kwds.join(',')}`)
}
newUrl.search = queryParams.length > 0 ? `?${queryParams.join('&')}` : ''
state.filteredUrl = newUrl.toString()
}
async function copyFilteredUrl () {
filteredUrlIpt.value?.select()
try {
if (navigator.clipboard) {
await navigator.clipboard.writeText(state.filteredUrl)
} else {
if (!document.execCommand('copy')) {
throw new Error('Copy failed')
}
}
message.success('URL copied to clipboard successfully.')
} catch (err) {
message.error('Failed to copy URL to clipboard.')
}
}
</script>
<style lang="scss">
@import "bootstrap/scss/functions";
@import "bootstrap/scss/variables";
.agenda-share {
width: 90vw;
max-width: 1000px;
&-header {
font-size: 20px;
display: flex;
align-items: center;
> .bi {
margin-right: 12px;
font-size: 20px;
color: $indigo;
}
}
&-close .bi {
font-size: 20px;
color: inherit;
}
}
</style>

View file

@ -4,6 +4,7 @@ import uniqBy from 'lodash/uniqBy'
import murmur from 'murmurhash-js/murmurhash3_gc' import murmur from 'murmurhash-js/murmurhash3_gc'
import { useSiteStore } from '../shared/store' import { useSiteStore } from '../shared/store'
import { storageAvailable } from '../shared/feature-detect'
const urlRe = /http[s]?:\/\/(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+/ const urlRe = /http[s]?:\/\/(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+/
const conferenceDomains = ['webex.com', 'zoom.us', 'jitsi.org', 'meetecho.com', 'gather.town'] const conferenceDomains = ['webex.com', 'zoom.us', 'jitsi.org', 'meetecho.com', 'gather.town']
@ -147,7 +148,11 @@ export const useAgendaStore = defineStore('agenda', {
const agendaData = await resp.json() const agendaData = await resp.json()
// -> Switch to meeting timezone // -> Switch to meeting timezone
this.timezone = window.localStorage.getItem(`agenda.${agendaData.meeting.number}.timezone`) || agendaData.meeting.timezone if (storageAvailable('localStorage')) {
this.timezone = window.localStorage.getItem(`agenda.${agendaData.meeting.number}.timezone`) || agendaData.meeting.timezone
} else {
this.timezone = agendaData.meeting.timezone
}
// -> Load meeting data // -> Load meeting data
this.categories = agendaData.categories this.categories = agendaData.categories
@ -161,9 +166,17 @@ export const useAgendaStore = defineStore('agenda', {
this.infoNoteHash = murmur(agendaData.meeting.infoNote, 0).toString() this.infoNoteHash = murmur(agendaData.meeting.infoNote, 0).toString()
// -> Load meeting-specific preferences // -> Load meeting-specific preferences
this.infoNoteShown = !(window.localStorage.getItem(`agenda.${agendaData.meeting.number}.hideInfo`) === this.infoNoteHash) if (storageAvailable('localStorage')) {
this.colorAssignments = JSON.parse(window.localStorage.getItem(`agenda.${agendaData.meeting.number}.colorAssignments`) || '{}') this.infoNoteShown = !(window.localStorage.getItem(`agenda.${agendaData.meeting.number}.hideInfo`) === this.infoNoteHash)
this.pickedEvents = JSON.parse(window.localStorage.getItem(`agenda.${agendaData.meeting.number}.pickedEvents`) || '[]') this.colorAssignments = JSON.parse(window.localStorage.getItem(`agenda.${agendaData.meeting.number}.colorAssignments`) || '{}')
this.selectedCatSubs = JSON.parse(window.localStorage.getItem(`agenda.${agendaData.meeting.number}.filters`) || '[]')
this.pickedEvents = JSON.parse(window.localStorage.getItem(`agenda.${agendaData.meeting.number}.pickedEvents`) || '[]')
} else {
this.infoNoteShown = true
this.colorAssignments = {}
this.selectedCatSubs = []
this.pickedEvents = []
}
this.isLoaded = true this.isLoaded = true
} catch (err) { } catch (err) {
@ -177,12 +190,15 @@ export const useAgendaStore = defineStore('agenda', {
this.hideLoadingScreen() this.hideLoadingScreen()
}, },
persistMeetingPreferences () { persistMeetingPreferences () {
if (!storageAvailable('localStorage')) { return }
if (this.infoNoteShown) { if (this.infoNoteShown) {
window.localStorage.removeItem(`agenda.${this.meeting.number}.hideInfo`) window.localStorage.removeItem(`agenda.${this.meeting.number}.hideInfo`)
} else { } else {
window.localStorage.setItem(`agenda.${this.meeting.number}.hideInfo`, this.infoNoteHash) window.localStorage.setItem(`agenda.${this.meeting.number}.hideInfo`, this.infoNoteHash)
} }
window.localStorage.setItem(`agenda.${this.meeting.number}.colorAssignments`, JSON.stringify(this.colorAssignments)) window.localStorage.setItem(`agenda.${this.meeting.number}.colorAssignments`, JSON.stringify(this.colorAssignments))
window.localStorage.setItem(`agenda.${this.meeting.number}.filters`, JSON.stringify(this.selectedCatSubs))
window.localStorage.setItem(`agenda.${this.meeting.number}.pickedEvents`, JSON.stringify(this.pickedEvents)) window.localStorage.setItem(`agenda.${this.meeting.number}.pickedEvents`, JSON.stringify(this.pickedEvents))
window.localStorage.setItem(`agenda.${this.meeting.number}.timezone`, this.timezone) window.localStorage.setItem(`agenda.${this.meeting.number}.timezone`, this.timezone)
}, },
@ -221,10 +237,10 @@ export const useAgendaStore = defineStore('agenda', {
} }
}, },
persist: { persist: {
enabled: true, enabled: storageAvailable('localStorage'),
strategies: [ strategies: [
{ {
storage: localStorage, storage: storageAvailable('localStorage') ? localStorage : null,
paths: [ paths: [
'areaIndicatorsShown', 'areaIndicatorsShown',
'bolderText', 'bolderText',

113
client/agenda/tour.js Normal file
View file

@ -0,0 +1,113 @@
import Shepherd from 'shepherd.js'
import 'shepherd.js/dist/css/shepherd.css'
export function initTour ({ mobileMode, pickerMode }) {
const tour = new Shepherd.Tour({
useModalOverlay: true,
defaultStepOptions: {
classes: 'shepherd-theme-custom',
scrollTo: false,
modalOverlayOpeningPadding: 8,
modalOverlayOpeningRadius: 4,
popperOptions: {
modifiers: [
{
name: 'offset',
options: {
offset: [0,20]
}
}
]
}
}
})
const defaultButtons = [
{
text: 'Exit',
action: tour.cancel,
secondary: true
},
{
text: 'Next',
action: tour.next
}
]
// STEPS
tour.addSteps([
{
title: 'Filter Areas + Groups',
text: 'You can filter the list of sessions by areas or working groups you\'re interested in. The filters you select here also apply to the <strong>Calendar View</strong> and persist even if you come back to this page later.',
attachTo: {
element: mobileMode ? '.agenda-mobile-bar > button:first-child' : '#agenda-quickaccess-filterbyareagroups-btn',
on: mobileMode ? 'top' : 'left'
},
buttons: defaultButtons
},
{
title: 'Pick Sessions',
text: 'Alternatively select <strong>individual sessions</strong> from the list to build your own schedule.',
attachTo: {
element: pickerMode ? '.agenda-quickaccess-btnrow' : '#agenda-quickaccess-picksessions-btn',
on: 'left'
},
buttons: defaultButtons,
showOn: () => !mobileMode
},
{
title: 'Calendar View',
text: 'View the current list of sessions in a <strong>calendar view</strong>, by week or by day. The filters you selected above also apply in this view.',
attachTo: {
element: mobileMode ? '.agenda-mobile-bar > button:nth-child(2)' : '#agenda-quickaccess-calview-btn',
on: mobileMode ? 'top' : 'left'
},
buttons: defaultButtons
},
{
title: 'Add to your calendar',
text: 'Add the current list of sessions to your personal calendar application, in either <strong>webcal</strong> or <strong>ics</strong> format.',
attachTo: {
element: mobileMode ? '.agenda-mobile-bar > button:nth-child(3)' : '#agenda-quickaccess-addtocal-btn',
on: mobileMode ? 'top' : 'left'
},
buttons: defaultButtons
},
{
title: 'Search Events',
text: 'Filter the list of sessions by searching for <strong>specific keywords</strong> in the title, location, acronym, notes or group name. Click the button again to close the search and discard its filtering.',
attachTo: {
element: '.agenda-table-search',
on: 'top'
},
buttons: defaultButtons
},
{
title: 'Assign Colors to Events',
text: 'Assign colors to individual events to keep track of those you find interesting, wish to attend or define your own colors / descriptions from the <strong>Settings</strong> panel.',
attachTo: {
element: '.agenda-table-colorpicker',
on: 'top'
},
buttons: defaultButtons
},
{
title: 'Sessions',
text: 'View the session materials by either clicking on its title or using the <strong>Show meeting materials</strong> button on the right. You can locate the room holding this event on the floor plan by clicking on the location name.',
attachTo: {
element: () => document.querySelector('.agenda-table-display-event'),
on: 'top'
},
buttons: [
{
text: 'Finish',
action: tour.next
}
],
modalOverlayOpeningPadding: 0,
modalOverlayOpeningRadius: 2
}
])
return tour
}

View file

@ -29,6 +29,12 @@ export default createRouter({
return { name: 'agenda' } return { name: 'agenda' }
} }
}, },
{
path: '/meeting/:meetingNumber(\\d+)?/agenda-utc',
redirect: to => {
return { name: 'agenda', query: { ...to.query, tz: 'UTC' } }
}
},
{ {
path: '/meeting/:meetingNumber(\\d+)?/agenda/personalize', path: '/meeting/:meetingNumber(\\d+)?/agenda/personalize',
redirect: to => { redirect: to => {

View file

@ -0,0 +1,19 @@
const cache = {}
export function storageAvailable(type) {
if (Object.prototype.hasOwnProperty.call(cache, type)) {
return cache[type]
}
try {
let storage = window[type]
const x = '__storage_test__'
storage.setItem(x, x)
storage.removeItem(x)
cache[type] = true
return true
}
catch (e) {
cache[type] = false
return false
}
}

21
jsconfig.json Normal file
View file

@ -0,0 +1,21 @@
{
"compilerOptions": {
"target": "ES6",
},
"exclude": [
"node_modules",
".yarn",
".vite"
],
"include": [
"client/**/*",
"playwright/**/*"
],
"vueCompilerOptions": {
"target": 3,
"plugins": [
"@volar/vue-language-plugin-pug"
]
}
}

View file

@ -24,8 +24,8 @@
"@popperjs/core": "2.11.6", "@popperjs/core": "2.11.6",
"bootstrap": "5.2.2", "bootstrap": "5.2.2",
"bootstrap-icons": "1.9.1", "bootstrap-icons": "1.9.1",
"browser-fs-access": "0.31.0", "browser-fs-access": "0.31.1",
"caniuse-lite": "1.0.30001414", "caniuse-lite": "1.0.30001420",
"d3": "7.6.1", "d3": "7.6.1",
"file-saver": "2.0.5", "file-saver": "2.0.5",
"highcharts": "10.2.1", "highcharts": "10.2.1",
@ -37,47 +37,48 @@
"lodash-es": "4.17.21", "lodash-es": "4.17.21",
"luxon": "3.0.4", "luxon": "3.0.4",
"moment": "2.29.4", "moment": "2.29.4",
"moment-timezone": "0.5.37", "moment-timezone": "0.5.38",
"ms": "2.1.3", "ms": "2.1.3",
"murmurhash-js": "1.0.0", "murmurhash-js": "1.0.0",
"naive-ui": "2.33.3", "naive-ui": "2.33.5",
"pinia": "2.0.22", "pinia": "2.0.23",
"pinia-plugin-persist": "1.0.0", "pinia-plugin-persist": "1.0.0",
"select2": "4.1.0-rc.0", "select2": "4.1.0-rc.0",
"select2-bootstrap-5-theme": "1.3.0", "select2-bootstrap-5-theme": "1.3.0",
"send": "0.18.0", "send": "0.18.0",
"shepherd.js": "10.0.1",
"slugify": "1.6.5", "slugify": "1.6.5",
"sortablejs": "1.15.0", "sortablejs": "1.15.0",
"vue": "3.2.40", "vue": "3.2.41",
"vue-router": "4.1.5", "vue-router": "4.1.5",
"zxcvbn": "4.4.2" "zxcvbn": "4.4.2"
}, },
"devDependencies": { "devDependencies": {
"@faker-js/faker": "7.5.0", "@faker-js/faker": "7.6.0",
"@parcel/transformer-sass": "2.7.0", "@parcel/transformer-sass": "2.7.0",
"@percy/cli": "1.10.4", "@percy/cli": "1.11.0",
"@percy/cypress": "3.1.2", "@percy/cypress": "3.1.2",
"@vitejs/plugin-vue": "2.3.4", "@vitejs/plugin-vue": "3.1.2",
"@vue/test-utils": "2.1.0", "@vue/test-utils": "2.1.0",
"browserlist": "latest", "browserlist": "latest",
"c8": "7.12.0", "c8": "7.12.0",
"cypress": "10.9.0", "cypress": "10.9.0",
"cypress-real-events": "1.7.1", "cypress-real-events": "1.7.1",
"eslint": "8.24.0", "eslint": "8.25.0",
"eslint-config-standard": "17.0.0", "eslint-config-standard": "17.0.0",
"eslint-plugin-cypress": "2.12.1", "eslint-plugin-cypress": "2.12.1",
"eslint-plugin-import": "2.26.0", "eslint-plugin-import": "2.26.0",
"eslint-plugin-n": "15.3.0", "eslint-plugin-n": "15.3.0",
"eslint-plugin-node": "11.1.0", "eslint-plugin-node": "11.1.0",
"eslint-plugin-promise": "6.0.1", "eslint-plugin-promise": "6.1.0",
"eslint-plugin-vue": "9.5.1", "eslint-plugin-vue": "9.6.0",
"html-validate": "7.5.0", "html-validate": "7.6.0",
"jquery-migrate": "3.4.0", "jquery-migrate": "3.4.0",
"parcel": "2.7.0", "parcel": "2.7.0",
"pug": "3.0.2", "pug": "3.0.2",
"sass": "1.55.0", "sass": "1.55.0",
"seedrandom": "3.0.5", "seedrandom": "3.0.5",
"vite": "2.9.15" "vite": "3.1.8"
}, },
"targets": { "targets": {
"ietf": { "ietf": {

View file

@ -81,9 +81,15 @@ test.describe('past - desktop', () => {
await expect(navLocator.last()).toContainText('Plaintext') await expect(navLocator.last()).toContainText('Plaintext')
}) })
// SETTINGS BUTTON // RIGHT-SIDE BUTTONS
await expect(page.locator('.agenda .meeting-nav + button')).toContainText('Settings') await test.step('has the correct right side buttons', async () => {
const btnsLocator = page.locator('.agenda .agenda-topnav-right > button')
await expect(btnsLocator).toHaveCount(3)
await expect(btnsLocator.first()).toContainText('Help')
await expect(btnsLocator.nth(1)).toContainText('Share')
await expect(btnsLocator.last()).toContainText('Settings')
})
}) })
test('agenda schedule list header', async ({ page }) => { test('agenda schedule list header', async ({ page }) => {
@ -773,7 +779,7 @@ test.describe('past - desktop', () => {
test('agenda settings', async ({ page, browserName }) => { test('agenda settings', async ({ page, browserName }) => {
// Open dialog // Open dialog
await page.locator('.meeting-nav + button').click() await page.locator('.agenda-topnav-right > button:last-child').click()
await expect(page.locator('.agenda-settings')).toBeVisible() await expect(page.locator('.agenda-settings')).toBeVisible()
// Check header elements // Check header elements
await expect(page.locator('.agenda-settings .n-drawer-header__main > span')).toContainText('Agenda Settings') await expect(page.locator('.agenda-settings .n-drawer-header__main > span')).toContainText('Agenda Settings')
@ -898,6 +904,22 @@ test.describe('past - desktop', () => {
await expect(page.locator('.agenda-settings')).not.toBeVisible() await expect(page.locator('.agenda-settings')).not.toBeVisible()
}) })
// -> SHARE DIALOG
test('agenda share dialog', async ({ page }) => {
// Open dialog
await page.locator('.agenda-topnav-right > button:nth-child(2)').click()
await expect(page.locator('.agenda-share')).toBeVisible()
// Check header elements
await expect(page.locator('.agenda-share .n-card-header__main > .agenda-share-header > .bi')).toBeVisible()
await expect(page.locator('.agenda-share .n-card-header__main > .agenda-share-header > .bi + span')).toContainText('Share this view')
// Check input URL
await expect(page.locator('.agenda-share .agenda-share-content input[type=text]')).toHaveValue(`http://localhost:3000/meeting/${meetingData.meeting.number}/agenda`)
// Clicking X should close the dialog
await page.locator('.agenda-share .n-card-header__extra > .agenda-share-header > button').click()
await expect(page.locator('.agenda-share')).not.toBeVisible()
})
// -> ADD TO CALENDAR // -> ADD TO CALENDAR
test('agenda add to calendar', async ({ page }) => { test('agenda add to calendar', async ({ page }) => {
@ -1012,7 +1034,7 @@ test.describe('past - desktop', () => {
// Change color legend from settings dialog // Change color legend from settings dialog
// ---------------------------------------- // ----------------------------------------
// Open dialog // Open dialog
await page.locator('.meeting-nav + button').click() await page.locator('.agenda-topnav-right > button:last-child').click()
await expect(page.locator('.agenda-settings')).toBeVisible() await expect(page.locator('.agenda-settings')).toBeVisible()
// Toggle color legend switch // Toggle color legend switch
await colorLgdSwitchLocator.click() await colorLgdSwitchLocator.click()
@ -1051,7 +1073,7 @@ test.describe('past - desktop', () => {
// No colored dots should appear // No colored dots should appear
await expect(page.locator('.agenda .agenda-table-display-event .agenda-table-colorindicator')).toHaveCount(0) await expect(page.locator('.agenda .agenda-table-display-event .agenda-table-colorindicator')).toHaveCount(0)
// Clear all colors from Settings menu // Clear all colors from Settings menu
await page.locator('.meeting-nav + button').click() await page.locator('.agenda-topnav-right > button:last-child').click()
await expect(page.locator('.agenda-settings')).toBeVisible() await expect(page.locator('.agenda-settings')).toBeVisible()
await page.locator('.agenda-settings .agenda-settings-actions > button').first().click() await page.locator('.agenda-settings .agenda-settings-actions > button').first().click()
await page.locator('.n-dropdown-option:has-text("Clear Color")').click() await page.locator('.n-dropdown-option:has-text("Clear Color")').click()
@ -1295,7 +1317,7 @@ test.describe('live - desktop', () => {
test('live red line toggle', async ({ page }) => { test('live red line toggle', async ({ page }) => {
// Open settings dialog // Open settings dialog
await page.locator('.meeting-nav + button').click() await page.locator('.agenda-topnav-right > button:last-child').click()
await expect(page.locator('.agenda-settings')).toBeVisible() await expect(page.locator('.agenda-settings')).toBeVisible()
// Toggle red line switch // Toggle red line switch
const redlineSwitchLocator = page.locator('#agenda-settings-tgl-redline div[role=switch]') const redlineSwitchLocator = page.locator('#agenda-settings-tgl-redline div[role=switch]')

694
yarn.lock

File diff suppressed because it is too large Load diff