Skip to content

Commit 7705f6a

Browse files
authored
Merge pull request #7085 from LibreSign/backport/7024/stable33
[stable33] feat: show confetti setting
2 parents 4c0d371 + f76791e commit 7705f6a

15 files changed

Lines changed: 227 additions & 2 deletions

lib/Capabilities.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@
88

99
namespace OCA\Libresign;
1010

11+
use OCA\Libresign\AppInfo\Application;
1112
use OCA\Libresign\Service\Envelope\EnvelopeService;
1213
use OCA\Libresign\Service\SignatureTextService;
1314
use OCA\Libresign\Service\SignerElementsService;
1415
use OCP\App\IAppManager;
1516
use OCP\Capabilities\IPublicCapability;
17+
use OCP\IAppConfig;
1618

1719
/**
1820
* @psalm-import-type LibresignCapabilities from ResponseDefinitions
@@ -27,6 +29,7 @@ public function __construct(
2729
protected SignatureTextService $signatureTextService,
2830
protected IAppManager $appManager,
2931
protected EnvelopeService $envelopeService,
32+
protected IAppConfig $appConfig,
3033
) {
3134
}
3235

@@ -40,6 +43,7 @@ public function getCapabilities(): array {
4043
$capabilities = [
4144
'features' => self::FEATURES,
4245
'config' => [
46+
'show-confetti' => $this->appConfig->getValueBool(Application::APP_ID, 'show_confetti_after_signing', true),
4347
'sign-elements' => [
4448
'is-available' => $this->signerElementsService->isSignElementsAvailable(),
4549
'can-create-signature' => $this->signerElementsService->canCreateSignature(),

lib/ResponseDefinitions.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,7 @@
392392
* @psalm-type LibresignCapabilities = array{
393393
* features: list<string>,
394394
* config: array{
395+
* show-confetti: bool,
395396
* sign-elements: array{
396397
* is-available: bool,
397398
* can-create-signature: bool,

lib/Settings/Admin.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ public function getForm(): TemplateResponse {
8989
$this->initialState->provideInitialState('approval_group', $this->appConfig->getValueArray(Application::APP_ID, 'approval_group', ['admin']));
9090
$this->initialState->provideInitialState('envelope_enabled', $this->appConfig->getValueBool(Application::APP_ID, 'envelope_enabled', true));
9191
$this->initialState->provideInitialState('parallel_workers', $this->appConfig->getValueString(Application::APP_ID, 'parallel_workers', '4'));
92+
$this->initialState->provideInitialState('show_confetti_after_signing', $this->appConfig->getValueBool(Application::APP_ID, 'show_confetti_after_signing', true));
9293
return new TemplateResponse(Application::APP_ID, 'admin_settings');
9394
}
9495

openapi-administration.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,15 @@
3737
"config": {
3838
"type": "object",
3939
"required": [
40+
"show-confetti",
4041
"sign-elements",
4142
"envelope",
4243
"upload"
4344
],
4445
"properties": {
46+
"show-confetti": {
47+
"type": "boolean"
48+
},
4549
"sign-elements": {
4650
"type": "object",
4751
"required": [

openapi-full.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,15 @@
3737
"config": {
3838
"type": "object",
3939
"required": [
40+
"show-confetti",
4041
"sign-elements",
4142
"envelope",
4243
"upload"
4344
],
4445
"properties": {
46+
"show-confetti": {
47+
"type": "boolean"
48+
},
4549
"sign-elements": {
4650
"type": "object",
4751
"required": [

openapi.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,15 @@
3737
"config": {
3838
"type": "object",
3939
"required": [
40+
"show-confetti",
4041
"sign-elements",
4142
"envelope",
4243
"upload"
4344
],
4445
"properties": {
46+
"show-confetti": {
47+
"type": "boolean"
48+
},
4549
"sign-elements": {
4650
"type": "object",
4751
"required": [
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2026 LibreSign contributors
3+
* SPDX-License-Identifier: AGPL-3.0-or-later
4+
*/
5+
6+
import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'
7+
import { mount } from '@vue/test-utils'
8+
9+
const loadStateMock = vi.fn()
10+
11+
vi.mock('@nextcloud/initial-state', () => ({
12+
loadState: (...args: unknown[]) => loadStateMock(...args),
13+
}))
14+
15+
vi.mock('@nextcloud/l10n', () => ({
16+
t: vi.fn((_app: string, text: string) => text),
17+
translate: vi.fn((_app: string, text: string) => text),
18+
translatePlural: vi.fn((_app: string, singular: string, plural: string, count: number) => (count === 1 ? singular : plural)),
19+
n: vi.fn((_app: string, singular: string, plural: string, count: number) => (count === 1 ? singular : plural)),
20+
isRTL: vi.fn(() => false),
21+
getLanguage: vi.fn(() => 'en'),
22+
getLocale: vi.fn(() => 'en'),
23+
}))
24+
25+
let Confetti: unknown
26+
27+
beforeAll(async () => {
28+
;({ default: Confetti } = await import('../../../views/Settings/Confetti.vue'))
29+
})
30+
31+
describe('Confetti', () => {
32+
beforeEach(() => {
33+
loadStateMock.mockReset()
34+
})
35+
36+
it('defaults to true when state is not set', async () => {
37+
loadStateMock.mockImplementation((_app: string, key: string, fallback: unknown) => fallback)
38+
39+
const wrapper = mount(Confetti as never, {
40+
global: {
41+
stubs: {
42+
NcSettingsSection: { template: '<div><slot /></div>' },
43+
NcCheckboxRadioSwitch: { template: '<div><slot /></div>' },
44+
},
45+
},
46+
})
47+
48+
expect(wrapper.vm.showConfetti).toBe(true)
49+
})
50+
51+
it('reads show_confetti_after_signing from initial state', async () => {
52+
loadStateMock.mockImplementation((_app: string, key: string, fallback: unknown) => {
53+
if (key === 'show_confetti_after_signing') return true
54+
return fallback
55+
})
56+
57+
const wrapper = mount(Confetti as never, {
58+
global: {
59+
stubs: {
60+
NcSettingsSection: { template: '<div><slot /></div>' },
61+
NcCheckboxRadioSwitch: { template: '<div><slot /></div>' },
62+
},
63+
},
64+
})
65+
66+
expect(wrapper.vm.showConfetti).toBe(true)
67+
})
68+
69+
it('calls OCP.AppConfig.setValue with "1" when enabled', async () => {
70+
loadStateMock.mockImplementation((_app: string, key: string, fallback: unknown) => fallback)
71+
const setValueMock = vi.fn()
72+
vi.stubGlobal('OCP', { AppConfig: { setValue: setValueMock } })
73+
74+
const wrapper = mount(Confetti as never, {
75+
global: {
76+
stubs: {
77+
NcSettingsSection: { template: '<div><slot /></div>' },
78+
NcCheckboxRadioSwitch: { template: '<div><slot /></div>' },
79+
},
80+
},
81+
})
82+
83+
await wrapper.setData({ showConfetti: true })
84+
wrapper.vm.saveShowConfetti()
85+
86+
expect(setValueMock).toHaveBeenCalledWith('libresign', 'show_confetti_after_signing', '1')
87+
})
88+
89+
it('calls OCP.AppConfig.setValue with "0" when disabled', async () => {
90+
loadStateMock.mockImplementation((_app: string, key: string, fallback: unknown) => {
91+
if (key === 'show_confetti_after_signing') return true
92+
return fallback
93+
})
94+
const setValueMock = vi.fn()
95+
vi.stubGlobal('OCP', { AppConfig: { setValue: setValueMock } })
96+
97+
const wrapper = mount(Confetti as never, {
98+
global: {
99+
stubs: {
100+
NcSettingsSection: { template: '<div><slot /></div>' },
101+
NcCheckboxRadioSwitch: { template: '<div><slot /></div>' },
102+
},
103+
},
104+
})
105+
106+
await wrapper.setData({ showConfetti: false })
107+
wrapper.vm.saveShowConfetti()
108+
109+
expect(setValueMock).toHaveBeenCalledWith('libresign', 'show_confetti_after_signing', '0')
110+
})
111+
})

src/tests/views/Validation.spec.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import { afterEach, describe, expect, it, beforeEach, vi } from 'vitest'
77
import { shallowMount } from '@vue/test-utils'
88
import axios from '@nextcloud/axios'
9+
import { getCapabilities } from '@nextcloud/capabilities'
910
import JSConfetti from 'js-confetti'
1011
import Validation from '../../views/Validation.vue'
1112

@@ -104,6 +105,17 @@ const mockRouter = {
104105
push: vi.fn(),
105106
}
106107

108+
// Mock capabilities - show-confetti enabled by default so existing tests pass
109+
vi.mock('@nextcloud/capabilities', () => ({
110+
getCapabilities: vi.fn(() => ({
111+
libresign: {
112+
config: {
113+
'show-confetti': true,
114+
},
115+
},
116+
})),
117+
}))
118+
107119
// Mock initial state
108120
vi.mock('@nextcloud/initial-state', () => ({
109121
loadState: vi.fn((app, key, defaultValue) => defaultValue),
@@ -654,6 +666,19 @@ describe('Validation.vue - Business Logic', () => {
654666
wrapper.vm.handleValidationSuccess({ status: SIGNED_STATUS, signers: [] })
655667
expect(mockAddConfetti).not.toHaveBeenCalled()
656668
})
669+
670+
it('does not fire confetti when show-confetti capability is disabled', async () => {
671+
vi.mocked(getCapabilities).mockReturnValueOnce({
672+
libresign: {
673+
config: {
674+
'show-confetti': false,
675+
},
676+
},
677+
} as ReturnType<typeof getCapabilities>)
678+
vi.spyOn(wrapper.vm, 'isAfterSigned', 'get').mockReturnValue(true)
679+
wrapper.vm.handleValidationSuccess({ status: SIGNED_STATUS, signers: [] })
680+
expect(mockAddConfetti).not.toHaveBeenCalled()
681+
})
657682
})
658683

659684
describe('handleSigningComplete method', () => {

src/types/openapi/openapi-administration.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,7 @@ export type components = {
492492
Capabilities: {
493493
features: string[];
494494
config: {
495+
"show-confetti": boolean;
495496
"sign-elements": {
496497
"is-available": boolean;
497498
"can-create-signature": boolean;

src/types/openapi/openapi-full.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1527,6 +1527,7 @@ export type components = {
15271527
Capabilities: {
15281528
features: string[];
15291529
config: {
1530+
"show-confetti": boolean;
15301531
"sign-elements": {
15311532
"is-available": boolean;
15321533
"can-create-signature": boolean;

0 commit comments

Comments
 (0)