Skip to content

Commit edb0fa9

Browse files
committed
refactor(profile): migrate to script setup and Vue 3
Signed-off-by: Ferdinand Thiessen <[email protected]>
1 parent be8b2bf commit edb0fa9

File tree

12 files changed

+309
-193
lines changed

12 files changed

+309
-193
lines changed

apps/profile/lib/Controller/ProfilePageController.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ public function index(string $targetUserId): TemplateResponse {
104104

105105
$this->eventDispatcher->dispatchTyped(new BeforeTemplateRenderedEvent($targetUserId));
106106

107+
Util::addStyle('profile', 'main');
107108
Util::addScript('profile', 'main');
108109

109110
return new TemplateResponse(
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* SPDX-FileCopyrightText: Nextcloud GmbH and Nextcloud contributors
3+
* SPDX-License-Identifier: AGPL-3.0-or-later
4+
*/
5+
6+
import { render } from '@testing-library/vue'
7+
import { expect, test, vi } from 'vitest'
8+
import ProfileSection from './ProfileSection.vue'
9+
10+
window.customElements.define('test-element', class extends HTMLElement {
11+
user?: string
12+
callback?: (user?: string) => void
13+
14+
connectedCallback() {
15+
this.callback?.(this.user)
16+
}
17+
})
18+
19+
test('can render section component', async () => {
20+
const callback = vi.fn()
21+
const result = render(ProfileSection, {
22+
props: {
23+
userId: 'testuser',
24+
section: {
25+
id: 'test-section',
26+
order: 1,
27+
tagName: 'test-element',
28+
params: {
29+
callback,
30+
},
31+
},
32+
},
33+
})
34+
35+
// this basically covers everything we need to test:
36+
// 1. The custom element is rendered
37+
// 2. The custom params are passed to the custom element
38+
// 3. The user id is passed to the custom element
39+
expect(result.baseElement.querySelector('test-element')).toBeTruthy()
40+
expect(callback).toHaveBeenCalledWith('testuser')
41+
})
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<!--
2+
- SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
3+
- SPDX-License-Identifier: AGPL-3.0-or-later
4+
-->
5+
6+
<script setup lang="ts">
7+
import type { IProfileSection } from '../services/ProfileSections.ts'
8+
9+
defineProps<{
10+
section: IProfileSection
11+
userId: string | undefined
12+
}>()
13+
</script>
14+
15+
<template>
16+
<div :class="$style.profileSection">
17+
<component
18+
:is="section.tagName"
19+
v-bind.prop="section.params"
20+
:user.prop="userId" />
21+
</div>
22+
</template>
23+
24+
<style module>
25+
.profileSection {
26+
margin-top: 2rem;
27+
}
28+
</style>

apps/profile/src/main.ts

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,16 @@
1-
/**
1+
/*
22
* SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
33
* SPDX-License-Identifier: AGPL-3.0-or-later
44
*/
55

6-
import { getCSPNonce } from '@nextcloud/auth'
7-
import Vue from 'vue'
6+
import { createApp } from 'vue'
87
import ProfileApp from './views/ProfileApp.vue'
98
import ProfileSections from './services/ProfileSections.js'
109

11-
__webpack_nonce__ = getCSPNonce()
10+
import 'vite/module-preload-polyfill'
1211

13-
if (!window.OCA) {
14-
window.OCA = {}
15-
}
12+
window.OCA.Profile ??= {}
13+
window.OCA.Profile.ProfileSections = new ProfileSections()
1614

17-
if (!window.OCA.Core) {
18-
window.OCA.Core = {}
19-
}
20-
Object.assign(window.OCA.Core, { ProfileSections: new ProfileSections() })
21-
22-
const View = Vue.extend(ProfileApp)
23-
24-
window.addEventListener('DOMContentLoaded', () => {
25-
new View().$mount('#content')
26-
})
15+
const app = createApp(ProfileApp)
16+
app.mount('#content')
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* SPDX-FileCopyrightText: Nextcloud GmbH and Nextcloud contributors
3+
* SPDX-License-Identifier: AGPL-3.0-or-later
4+
*/
5+
6+
import type { IProfileSection } from './ProfileSections.ts'
7+
8+
import { expect, test, vi } from 'vitest'
9+
import ProfileSections from './ProfileSections.ts'
10+
11+
test('register profile section', () => {
12+
const profileSection: IProfileSection = {
13+
id: 'test-section',
14+
order: 1,
15+
tagName: 'test-element',
16+
}
17+
18+
const sections = new ProfileSections()
19+
sections.registerSection(profileSection)
20+
21+
expect(sections.getSections()).toHaveLength(1)
22+
expect(sections.getSections()[0]).toBe(profileSection)
23+
})
24+
25+
test('register profile section twice', () => {
26+
const profileSection: IProfileSection = {
27+
id: 'test-section',
28+
order: 1,
29+
tagName: 'test-element',
30+
}
31+
32+
const spy = vi.spyOn(console, 'warn').mockImplementation(() => {})
33+
34+
const sections = new ProfileSections()
35+
sections.registerSection(profileSection)
36+
sections.registerSection(profileSection)
37+
38+
expect(spy).toHaveBeenCalled()
39+
expect(sections.getSections()).toHaveLength(1)
40+
expect(sections.getSections()[0]).toBe(profileSection)
41+
})
Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,52 @@
11
/**
2-
* SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
2+
* SPDX-FileCopyrightText: Nextcloud GmbH and Nextcloud contributors
33
* SPDX-License-Identifier: AGPL-3.0-or-later
44
*/
55

6+
import { logger } from './logger.ts'
7+
8+
export interface IProfileSection {
9+
/**
10+
* Unique identifier for the section
11+
*/
12+
id: string
13+
/**
14+
* The order in which the section should appear
15+
*/
16+
order: number
17+
/**
18+
* The custom element tag name to be used for this section
19+
*
20+
* The custom element must have been registered beforehand,
21+
* and must have the a `user` property of type `string | undefined`.
22+
*
23+
* @see https://developer.mozilla.org/en-US/docs/Web/API/Web_components
24+
*/
25+
tagName: string
26+
/**
27+
* Static parameters to be passed to the custom web component
28+
*/
29+
params?: Record<string, unknown>
30+
}
31+
632
export default class ProfileSections {
7-
_sections
33+
#sections: Map<string, IProfileSection>
834

935
constructor() {
10-
this._sections = []
36+
this.#sections = new Map()
1137
}
1238

1339
/**
1440
* @param section To be called to mount the section to the profile page
1541
*/
16-
registerSection(section) {
17-
this._sections.push(section)
42+
registerSection(section: IProfileSection) {
43+
if (this.#sections.has(section.id)) {
44+
logger.warn(`Profile section with id '${section.id}' is already registered.`)
45+
}
46+
this.#sections.set(section.id, section)
1847
}
1948

2049
getSections() {
21-
return this._sections
50+
return [...this.#sections.values()]
2251
}
2352
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/*
2+
* SPDX-FileCopyrightText: Nextcloud GmbH and Nextcloud contributors
3+
* SPDX-License-Identifier: AGPL-3.0-or-later
4+
*/
5+
6+
import { expect, test, vi } from 'vitest'
7+
import { logger } from './logger.ts'
8+
9+
test('logger', () => {
10+
const spy = vi.spyOn(console, 'warn').mockImplementation(() => {})
11+
logger.warn('This is a warning message')
12+
13+
expect(console.warn).toHaveBeenCalled()
14+
expect(spy.mock.calls[0]![0]).toContain('profile')
15+
})
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/*
2+
* SPDX-FileCopyrightText: Nextcloud GmbH and Nextcloud contributors
3+
* SPDX-License-Identifier: AGPL-3.0-or-later
4+
*/
5+
6+
import { getLoggerBuilder } from '@nextcloud/logger'
7+
8+
export const logger = getLoggerBuilder()
9+
.setApp('profile')
10+
.detectLogLevel()
11+
.build()

0 commit comments

Comments
 (0)