Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/profile/lib/Controller/ProfilePageController.php
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ public function index(string $targetUserId): TemplateResponse {

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

Util::addStyle('profile', 'main');
Util::addScript('profile', 'main');

return new TemplateResponse(
Expand Down
41 changes: 41 additions & 0 deletions apps/profile/src/components/ProfileSection.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* SPDX-FileCopyrightText: Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import { render } from '@testing-library/vue'
import { expect, test, vi } from 'vitest'
import ProfileSection from './ProfileSection.vue'

window.customElements.define('test-element', class extends HTMLElement {
user?: string
callback?: (user?: string) => void

connectedCallback() {
this.callback?.(this.user)
}
})

test('can render section component', async () => {
const callback = vi.fn()
const result = render(ProfileSection, {
props: {
userId: 'testuser',
section: {
id: 'test-section',
order: 1,
tagName: 'test-element',
params: {
callback,
},
},
},
})

// this basically covers everything we need to test:
// 1. The custom element is rendered
// 2. The custom params are passed to the custom element
// 3. The user id is passed to the custom element
expect(result.baseElement.querySelector('test-element')).toBeTruthy()
expect(callback).toHaveBeenCalledWith('testuser')
})
28 changes: 28 additions & 0 deletions apps/profile/src/components/ProfileSection.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<!--
- SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
- SPDX-License-Identifier: AGPL-3.0-or-later
-->

<script setup lang="ts">
import type { IProfileSection } from '../services/ProfileSections.ts'

defineProps<{
section: IProfileSection
userId: string | undefined
}>()
</script>

<template>
<div :class="$style.profileSection">
<component
:is="section.tagName"
v-bind.prop="section.params"
:user.prop="userId" />
</div>
</template>

<style module>
.profileSection {
margin-top: 2rem;
}
</style>
24 changes: 7 additions & 17 deletions apps/profile/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,16 @@
/**
/*
* SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import { getCSPNonce } from '@nextcloud/auth'
import Vue from 'vue'
import { createApp } from 'vue'
import ProfileApp from './views/ProfileApp.vue'
import ProfileSections from './services/ProfileSections.js'

__webpack_nonce__ = getCSPNonce()
import 'vite/modulepreload-polyfill'

if (!window.OCA) {
window.OCA = {}
}
window.OCA.Profile ??= {}
window.OCA.Profile.ProfileSections = new ProfileSections()

if (!window.OCA.Core) {
window.OCA.Core = {}
}
Object.assign(window.OCA.Core, { ProfileSections: new ProfileSections() })

const View = Vue.extend(ProfileApp)

window.addEventListener('DOMContentLoaded', () => {
new View().$mount('#content')
})
const app = createApp(ProfileApp)
app.mount('#content')
41 changes: 41 additions & 0 deletions apps/profile/src/services/ProfileSections.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* SPDX-FileCopyrightText: Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import type { IProfileSection } from './ProfileSections.ts'

import { expect, test, vi } from 'vitest'
import ProfileSections from './ProfileSections.ts'

test('register profile section', () => {
const profileSection: IProfileSection = {
id: 'test-section',
order: 1,
tagName: 'test-element',
}

const sections = new ProfileSections()
sections.registerSection(profileSection)

expect(sections.getSections()).toHaveLength(1)
expect(sections.getSections()[0]).toBe(profileSection)
})

test('register profile section twice', () => {
const profileSection: IProfileSection = {
id: 'test-section',
order: 1,
tagName: 'test-element',
}

const spy = vi.spyOn(console, 'warn').mockImplementation(() => {})

const sections = new ProfileSections()
sections.registerSection(profileSection)
sections.registerSection(profileSection)

expect(spy).toHaveBeenCalled()
expect(sections.getSections()).toHaveLength(1)
expect(sections.getSections()[0]).toBe(profileSection)
})
41 changes: 35 additions & 6 deletions apps/profile/src/services/ProfileSections.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,52 @@
/**
* SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import { logger } from './logger.ts'

export interface IProfileSection {
/**
* Unique identifier for the section
*/
id: string
/**
* The order in which the section should appear
*/
order: number
/**
* The custom element tag name to be used for this section
*
* The custom element must have been registered beforehand,
* and must have the a `user` property of type `string | undefined`.
*
* @see https://developer.mozilla.org/en-US/docs/Web/API/Web_components
*/
tagName: string
/**
* Static parameters to be passed to the custom web component
*/
params?: Record<string, unknown>
}

export default class ProfileSections {
_sections
#sections: Map<string, IProfileSection>

constructor() {
this._sections = []
this.#sections = new Map()
}

/**
* @param section To be called to mount the section to the profile page
*/
registerSection(section) {
this._sections.push(section)
registerSection(section: IProfileSection) {
if (this.#sections.has(section.id)) {
logger.warn(`Profile section with id '${section.id}' is already registered.`)
}
this.#sections.set(section.id, section)
}

getSections() {
return this._sections
return [...this.#sections.values()]
}
}
15 changes: 15 additions & 0 deletions apps/profile/src/services/logger.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
* SPDX-FileCopyrightText: Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import { expect, test, vi } from 'vitest'
import { logger } from './logger.ts'

test('logger', () => {
const spy = vi.spyOn(console, 'warn').mockImplementation(() => {})
logger.warn('This is a warning message')

expect(console.warn).toHaveBeenCalled()
expect(spy.mock.calls[0]![0]).toContain('profile')
})
11 changes: 11 additions & 0 deletions apps/profile/src/services/logger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* SPDX-FileCopyrightText: Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import { getLoggerBuilder } from '@nextcloud/logger'

export const logger = getLoggerBuilder()
.setApp('profile')
.detectLogLevel()
.build()
Loading
Loading