Skip to content

Commit

Permalink
move theme to web-runtime and get it closer where it belongs to
Browse files Browse the repository at this point in the history
extract theme loading into own helper method
add test to verify theme loading is working as expected
fix theme loading, before it has overwritten the whole theme object, now we respect the values and keys that are set-able.
add init step for fetchMock to use mocks in testing by default
clean up config test to use global fetch
to be able to reset the store in tests we started to use createStore from vuex-extensions which offers a reset method to get back the initial state
fine-tune jest to ignore lodash-es which is problematic in some cases
introduce web-pkg which provides globally used functionality
add and write tests for keysDeep which is similar to lodash keys but deep
rename web-runtime Top-Bar component to match style
move TopBar tests to web-runtime
move web-runtime config to helpers folder
move config spec from toplevel to package level
bump core-js
  • Loading branch information
fschade committed Apr 6, 2021
1 parent eb821de commit 183f838
Show file tree
Hide file tree
Showing 28 changed files with 194 additions and 42 deletions.
6 changes: 6 additions & 0 deletions changelog/unreleased/change-introduce-web-pkg
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Change: Add web-pkg package

We added web-pkg as a new package.
It is supposed to be the central location for reuse of generic functionality.

https://github.com/owncloud/web/pull/4907
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"packages/web-app-files",
"packages/web-app-markdown-editor",
"packages/web-app-media-viewer",
"packages/web-pkg",
"packages/web-runtime"
],
"scripts": {
Expand Down Expand Up @@ -45,7 +46,7 @@
"babel-eslint": "^10.1.0",
"babel-jest": "^26.6.3",
"chromedriver": "^89.0.0",
"core-js": "3",
"core-js": "^3.10.0",
"cucumber": ">=6.0.5",
"cucumber-pretty": ">=6.0.0",
"depcheck": "^1.3.1",
Expand Down Expand Up @@ -80,13 +81,13 @@
"rollup-plugin-copy-watch": "^0.0.1",
"rollup-plugin-delete": "^2.0.0",
"rollup-plugin-gzip": "^2.5.0",
"rollup-plugin-livereload": "^2.0.0",
"rollup-plugin-modify": "^3.0.0",
"rollup-plugin-node-globals": "^1.4.0",
"rollup-plugin-node-resolve": "^5.2.0",
"rollup-plugin-postcss": "^4.0.0",
"rollup-plugin-progress": "^1.1.2",
"rollup-plugin-serve": "^1.1.0",
"rollup-plugin-livereload": "^2.0.0",
"rollup-plugin-terser": "^7.0.2",
"rollup-plugin-visualizer": "^4.2.0",
"rollup-plugin-vue": "^5.1.4",
Expand Down
9 changes: 9 additions & 0 deletions packages/web-pkg/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name": "web-pkg",
"version": "0.0.0",
"description": "ownCloud web pkg",
"license": "AGPL-3.0",
"devDependencies": {
"lodash-es": "^4.17.20"
}
}
17 changes: 17 additions & 0 deletions packages/web-pkg/src/utils/object.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { isPlainObject } from 'lodash'

export const keysDeep = obj => {
const paths = []

const walk = (o, p = '') =>
Object.keys(o).forEach(key => {
if (isPlainObject(o[key])) {
walk(o[key], `${p}${key}.`)
} else {
paths.push(`${p}${key}`)
}
})

walk(obj)
return paths
}
19 changes: 19 additions & 0 deletions packages/web-pkg/tests/utils/object.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { keysDeep } from 'web-pkg/src/utils/object'

describe('keysDeep', () => {
it('should return the correct keys', () => {
expect(
keysDeep({
foo1: { bar1: { baz1: 1, baz2: 1 }, bar2: { baz1: 1, baz2: 1 }, bar3: 1 },
foo2: 1
})
).toMatchObject([
'foo1.bar1.baz1',
'foo1.bar1.baz2',
'foo1.bar2.baz1',
'foo1.bar2.baz2',
'foo1.bar3',
'foo2'
])
})
})
2 changes: 2 additions & 0 deletions packages/web-runtime/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@
"vue-scrollto": "^2.15.0",
"vue2-touch-events": "^2.2.1",
"vuex": "^3.1.1",
"vuex-extensions": "^1.1.5",
"vuex-persist": "2.0.1",
"vuex-router-sync": "^5.0.0",
"web-pkg": "*",
"wicked-good-xpath": "^1.3.0"
}
}
2 changes: 1 addition & 1 deletion packages/web-runtime/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@
<script>
import 'inert-polyfill'
import { mapGetters, mapState, mapActions } from 'vuex'
import TopBar from './components/Top-Bar.vue'
import TopBar from './components/TopBar.vue'
import MessageBar from './components/MessageBar.vue'
import SkipTo from './components/SkipTo.vue'
import moment from 'moment'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,3 @@ export const loadConfig = async () => {
}
return config
}

export default {
loadConfig: loadConfig
}
22 changes: 22 additions & 0 deletions packages/web-runtime/src/helpers/theme.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import defaultTheme from 'web-runtime/themes/owncloud.json'
export const loadTheme = async (name = '') => {
const defaults = { theme: defaultTheme, name: 'themes/owncloud/theme.json' }

if (name === '' || name === defaults.name) {
return defaults
}

let response
try {
response = await fetch(name)
} catch (e) {
return defaults
}

if (!response.ok) {
return defaults
}

const customTheme = await response.json()
return { theme: customTheme, name }
}
8 changes: 4 additions & 4 deletions packages/web-runtime/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ import wgxpath from 'wicked-good-xpath'

import { registerClient } from './services/clientRegistration'

import { loadConfig } from './configHelper'
import { loadConfig } from './helpers/config'
import { loadTheme } from './helpers/theme'

wgxpath.install()

Expand Down Expand Up @@ -184,10 +185,9 @@ const finalizeInit = async () => {
}

const fetchTheme = async (themeName = 'owncloud') => {
const response = await fetch(`themes/${themeName}.json`)
const theme = await response.json()
const { theme, name } = await loadTheme(`themes/${themeName}.json`)

await store.dispatch('loadTheme', { theme, name: themeName })
await store.dispatch('loadTheme', { theme, name })
}

const missingOrInvalidConfig = async () => {
Expand Down
3 changes: 2 additions & 1 deletion packages/web-runtime/src/store/config.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import isEmpty from 'lodash-es/isEmpty'
import merge from 'lodash-es/merge'

const state = {
state: null,
Expand Down Expand Up @@ -84,7 +85,7 @@ const mutations = {
}
},
LOAD_THEME(state, theme) {
state.theme = { ...state.theme, ...theme }
state.theme = merge({}, state.theme, theme)
}
}

Expand Down
3 changes: 2 additions & 1 deletion packages/web-runtime/src/store/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import Vue from 'vue'
import Vuex from 'vuex'
import VuexPersistence from 'vuex-persist'
import { createStore } from 'vuex-extensions'

/* STORE MODULES
*/
Expand Down Expand Up @@ -31,7 +32,7 @@ const vuexPersistInSession = new VuexPersistence({

const strict = process.env.NODE_ENV === 'development'

export const Store = new Vuex.Store({
export const Store = createStore(Vuex.Store, {
plugins: [vuexPersistInSession.plugin],
modules: {
app,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import { shallowMount, createLocalVue } from '@vue/test-utils'
import Vuex from 'vuex'

import Stubs from '../config/stubs'

import TopBar from 'web-runtime/src/components/Top-Bar.vue'
import TopBar from 'web-runtime/src/components/TopBar.vue'
import stubs from '../../../../tests/unit/config/stubs'

const localVue = createLocalVue()
const search = enabled => ({
Expand All @@ -21,7 +19,7 @@ describe('Top Bar component', () => {
}
}),
localVue,
stubs: Stubs,
stubs,
propsData: {
userId: 'einstein',
userDisplayName: 'Albert Einstein'
Expand All @@ -40,7 +38,7 @@ describe('Top Bar component', () => {
}
}),
localVue,
stubs: Stubs,
stubs,
propsData: {
userId: 'einstein',
userDisplayName: 'Albert Einstein',
Expand All @@ -60,7 +58,7 @@ describe('Top Bar component', () => {
}
}),
localVue,
stubs: Stubs
stubs
})

wrapper.find('.oc-app-navigation-toggle').vm.$emit('click')
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
import { loadConfig } from 'web-runtime/src/configHelper'
import fetchMock from 'jest-fetch-mock'

fetchMock.enableMocks()
import { loadConfig } from 'web-runtime/src/helpers/config'

const validConfig = `{
"server" : "http://localhost/owncloud-core",
Expand Down Expand Up @@ -31,14 +28,14 @@ const validConfig = `{

describe('config file loading and error reporting', () => {
it('should load and parse a valid config', function() {
fetchMock.mockOnce(validConfig)
fetch.mockResponseOnce(validConfig)
return loadConfig().then(async result => {
expect(await result).toMatchObject(JSON.parse(validConfig))
})
})
describe('empty config', () => {
it('should throw an exception', function() {
fetchMock.mockOnce('')
fetch.mockResponseOnce('')
return expect(loadConfig).rejects.toThrow(
'config could not be parsed. ' +
'FetchError: invalid json response body at ' +
Expand All @@ -48,7 +45,7 @@ describe('config file loading and error reporting', () => {
})
describe('config with an trailing comma', () => {
it('should throw an exception', function() {
fetchMock.mockOnce('"title": { "en": "Classic Design", "de": "Dateien", },')
fetch.mockResponseOnce('"title": { "en": "Classic Design", "de": "Dateien", },')
return expect(loadConfig).rejects.toThrow(
'config could not be parsed. ' +
'FetchError: invalid json response body at ' +
Expand All @@ -59,7 +56,7 @@ describe('config file loading and error reporting', () => {
})
describe('missing config', () => {
it('should throw an exception', function() {
fetchMock.mockOnce(
fetch.mockResponseOnce(
'<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">\n' +
'<html><head>\n' +
'<title>404 Not Found</title>\n' +
Expand Down
42 changes: 42 additions & 0 deletions packages/web-runtime/tests/helpers/theme.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { loadTheme } from 'web-runtime/src/helpers/theme'
import { merge } from 'lodash'
import defaultTheme from 'web-runtime/themes/owncloud.json'
const defaultName = 'themes/owncloud/theme.json'

describe('theme loading and error reporting', () => {
it('should load the default theme if no name is given', async () => {
const { theme, name } = await loadTheme()
expect(theme).toMatchObject(defaultTheme)
expect(name).toMatch(defaultName)
})

it('should load the default theme if default name is given', async () => {
const { theme, name } = await loadTheme(defaultName)
expect(theme).toMatchObject(defaultTheme)
expect(name).toMatch(defaultName)
})

it('should load the default theme if name is unknown', async () => {
fetch.mockResponseOnce(new Error(), { status: 404 })
const { theme, name } = await loadTheme('unknown.json')
expect(theme).toMatchObject(defaultTheme)
expect(name).toMatch(defaultName)
})

it('should load the default theme if server errors', async () => {
fetch.mockRejectOnce(new Error())
const { theme, name } = await loadTheme('something.json')
expect(theme).toMatchObject(defaultTheme)
expect(name).toMatch(defaultName)
})

it('should load the custom theme if a custom name is given', async () => {
const customTheme = merge({}, defaultTheme, { logo: { login: 'custom.svg' } })
const customName = 'themes/custom/theme.json'
fetch.mockResponseOnce(JSON.stringify(customTheme))

const { theme, name } = await loadTheme(customName)
expect(theme).toMatchObject(customTheme)
expect(name).toMatch(customName)
})
})
34 changes: 34 additions & 0 deletions packages/web-runtime/tests/store/config.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { loadTheme } from 'web-runtime/src/helpers/theme'
import store from 'web-runtime/src/store'
import { keysDeep } from 'web-pkg/src/utils/object'
import get from 'lodash-es/get'
import difference from 'lodash-es/difference'

describe('config theme bootstrap', () => {
const initialStoreTheme = { ...store.getters.configuration.theme }

beforeEach(() => {
store.reset()
})

it('should be able to loadTheme', async () => {
const { theme, name } = await loadTheme()

await store.dispatch('loadTheme', { theme, name })
keysDeep(theme).forEach(k => {
expect(get(store.getters.configuration.theme, k)).toBe(get(theme, k))
})
})

it('should not overwrite keys that are not part of theme', async () => {
const { theme, name } = await loadTheme()
const storeThemeKeys = keysDeep(store.getters.configuration.theme)
const loadedThemeKeys = keysDeep(theme)
const diffThemeKeys = difference(storeThemeKeys, loadedThemeKeys)
await store.dispatch('loadTheme', { theme, name })

diffThemeKeys.forEach(k => {
expect(get(store.getters.configuration.theme, k)).toBe(get(initialStoreTheme, k))
})
})
})
File renamed without changes.
10 changes: 6 additions & 4 deletions rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,10 @@ const plugins = [
watch: !production && './config',
targets: [
{ src: './packages/web-container/img', dest: 'dist' },
{ src: './packages/web-container/themes', dest: 'dist' },
{ src: './packages/web-container/oidc-callback.html', dest: 'dist' },
{ src: './packages/web-container/oidc-silent-redirect.html', dest: 'dist' },
{ src: './packages/web-container/manifest.json', dest: 'dist' },
{ src: './packages/web-runtime/themes', dest: 'dist' },
{ src: `./config/${production ? 'config.dist.json' : 'config.json'}`, dest: 'dist' },
{ src: 'node_modules/requirejs/require.js', dest: 'dist/js' }
]
Expand Down Expand Up @@ -154,9 +154,11 @@ if (process.env.SERVER === 'true') {
port: process.env.PORT || 9100
})
)
plugins.push(livereload({
watch: 'dist',
}))
plugins.push(
livereload({
watch: 'dist'
})
)
}

if (process.env.REPORT === 'true') {
Expand Down
1 change: 1 addition & 0 deletions tests/unit/config/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ module.exports = {
'.*\\.(vue)$': 'vue-jest',
'^.+\\.svg$': 'jest-svg-transformer'
},
transformIgnorePatterns: ['<rootDir>/node_modules/(?!lodash-es)'],
setupFiles: ['<rootDir>/tests/unit/config/jest.init.js'],
snapshotSerializers: ['jest-serializer-vue'],
coverageDirectory: '<rootDir>/coverage',
Expand Down
5 changes: 3 additions & 2 deletions tests/unit/config/jest.init.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { config } from '@vue/test-utils'
import fetchMock from 'jest-fetch-mock'

const $gettext = str => str
fetchMock.enableMocks()

config.mocks = {
$gettext
$gettext: str => str
}
Loading

0 comments on commit 183f838

Please sign in to comment.