Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
b9bebeb
fix(calendar): use toJSDate() for proper timezone conversion
piratefinn Mar 23, 2026
49c145e
fix(calendar): handle floating time events in user's timezone
piratefinn Mar 24, 2026
3219cd3
fix(calendar): changes made to datetime stuff, should be more correct…
piratefinn Mar 24, 2026
d074338
fix(calendar): improve floating time handling and fix test jCal format
piratefinn Mar 25, 2026
8333fd9
fix(calendar): make GH-181 test DST-aware with dynamic Stockholm offset
piratefinn Apr 1, 2026
bc35b3d
test(calendar): add E2E tests for timezone handling
piratefinn Apr 1, 2026
605271e
fix(calendar): use correct ICS property parameter separator in E2E tests
piratefinn Apr 2, 2026
d79234a
fix(calendar): strip milliseconds from test ISO strings to fix CI tim…
piratefinn Apr 2, 2026
73d4d88
fix(calendar): fix timezone handling when account tz differs from bro…
piratefinn Apr 16, 2026
2b973a3
fix(calendar): make E2E timezone tests work in any browser timezone
piratefinn Apr 16, 2026
9a5ff01
feat(signup): Angular version of signup page using existing backend.
gtandersen Apr 28, 2026
178ebf1
fix(calendar): fix all-day date shift, citadel-path TZID, and all-day…
piratefinn May 1, 2026
6f4c890
refactor(calendar): tighten types and remove dead null checks in even…
piratefinn May 1, 2026
200b091
fix(calendar): harden timezone tests and simplify all-day end getter
piratefinn May 5, 2026
69163f6
refactor(calendar): clean up timezone test helpers and fix offset for…
piratefinn May 5, 2026
db714d8
fix(calendar): use calendar-prefixed event IDs in mockserver PUT handler
piratefinn May 5, 2026
b5753aa
fix(signup): Add missing license header.
gtandersen May 5, 2026
e4aa9a8
fix(signup): Correct signup file license headers for policy test
gtandersen May 5, 2026
f700658
fix(test): Attempt to fix failing test for AliasesListerComponent.
gtandersen May 5, 2026
4b57888
fix(test): Attempt to fix failing tests for AliasesEditorModalComponent.
gtandersen May 5, 2026
a633575
fix(calendar): wait for initial events sync before timezone tests
piratefinn May 5, 2026
0272ea8
fix(test): Attempt to fix failing tests for HelpComponent.
gtandersen May 5, 2026
107dcf7
fix(test): Fix CI unit tests by aligning Angular and preferences mocks
gtandersen May 5, 2026
bbd417c
fix(calendar): fix flaky all-day event creation E2E test
piratefinn May 6, 2026
437e677
Merge remote-tracking branch 'upstream/piratefinn/calendar-timezone-f…
gtandersen May 6, 2026
5d60ad0
fix(test): Fix missing Angular test imports in WelcomeDesk spec.
gtandersen May 6, 2026
749104b
test(signup): Stabilize signup custom domain validation test.
gtandersen May 6, 2026
6a6e049
fix(test): Silence expected websocket error log in unit test.
gtandersen May 6, 2026
04a80fa
fix(test): Add Material list module to SingleMailViewer spec.
gtandersen May 6, 2026
61a79f7
fix(test): Fix flaky preferences uid test by replaying mocked uid.
gtandersen May 6, 2026
54d1eee
fix(test): Remove fakeAsync from aliases dialog default email test.
gtandersen May 6, 2026
77297a1
fix(test): Add MatIconModule to WelcomeDesk spec.
gtandersen May 6, 2026
e41ae81
fix(test): Stabilize aliases allowed domains dialog test.
gtandersen May 6, 2026
6b40bb6
test(signup): Use real form submit in signup validation test.
gtandersen May 6, 2026
f728f35
test(signup): Split signup Cypress tests into CI and deployed variants.
gtandersen May 6, 2026
a4da6e4
test(signup): Use local signup route in CI Cypress spec.
gtandersen May 6, 2026
42c02d7
Merge branch 'master' into geir/signup-screen
gtandersen May 21, 2026
5a84196
fix(signup): Fix signup route fallback and update legacy Material Sas…
gtandersen May 22, 2026
6e96ead
fix(signup): Refine signup tooltip triggers and popover positioning.
gtandersen May 22, 2026
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
408 changes: 408 additions & 0 deletions e2e/cypress/integration/calendar-timezone.ts

Large diffs are not rendered by default.

100 changes: 100 additions & 0 deletions e2e/cypress/integration/signup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// --------- BEGIN RUNBOX LICENSE ---------
// Copyright (C) 2016-2026 Runbox Solutions AS (runbox.com).
//
// This file is part of Runbox 7.
//
// Runbox 7 is free software: You can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the
// Free Software Foundation, either version 3 of the License, or (at your
// option) any later version.
//
// Runbox 7 is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Runbox 7. If not, see <https://www.gnu.org/licenses/>.
// ---------- END RUNBOX LICENSE ----------

/// <reference types="cypress" />

describe('Signup', () => {
beforeEach(() => {
cy.intercept('GET', '/signup?legacy=1&runbox7=1', {
statusCode: 200,
body: `
<html>
<body>
<form name="signup" action="/mail/signup">
<select name="runboxDomain">
<option value="runbox.com">runbox.com</option>
<option value="runbox.no">runbox.no</option>
<option value="rbx.email">rbx.email</option>
</select>
<div class="h-captcha" data-sitekey="test-site-key"></div>
</form>
</body>
</html>
`,
headers: {
'content-type': 'text/html',
},
}).as('legacySignup');

cy.intercept('GET', 'https://hcaptcha.com/1/api.js?render=explicit', {
statusCode: 200,
body: 'window.hcaptcha = { render: function() { return "test-widget"; } };',
headers: {
'content-type': 'application/javascript',
},
}).as('hcaptchaScript');
});

it('should render the Angular signup page in the local mock-backed environment', () => {
cy.visit('/signup?runbox7=1');
cy.wait('@legacySignup');
cy.wait('@hcaptchaScript');

cy.location('pathname').should('eq', '/signup');
cy.location('search').should('contain', 'runbox7=1');
cy.contains('h1', 'Create a Runbox Account').should('exist');
cy.get('form.signup-form').should('have.attr', 'action', '/mail/signup');
cy.get('input[name="user"]').should('exist');
cy.get('input[name="first_name"]').should('exist');
cy.get('input[name="last_name"]').should('exist');
cy.get('input[name="password"]').should('exist');
cy.get('select[name="runboxDomain"]').find('option').should('have.length', 3);
cy.get('select[name="runboxDomain"]').find('option').then((options) => {
const domains = Array.from(options).map((option) => option.value);
expect(domains).to.include('runbox.com');
expect(domains).to.include('rbx.email');
});
cy.get('div.captcha-host').should('exist');
cy.contains('button.submit', 'Set up my Runbox account').should('exist');
});

it('should show the public trust and transparency content', () => {
cy.visit('/signup?runbox7=1');
cy.wait('@legacySignup');
cy.wait('@hcaptchaScript');

cy.get('header.signup-header .brand img').should('be.visible');
cy.get('header.signup-header .brand').should('not.contain', 'Runbox 7');

cy.contains('.hero-panel h2', 'Privacy by business model').should('exist');
cy.contains('.hero-panel h2', 'Hosted in Norway').should('exist');
cy.contains('.hero-panel h2', 'Sustainable and secure').should('exist');
cy.contains('.hero-panel h2', 'How the trial works').should('exist');

cy.contains('.hero-panel', 'customer email content is private').should('exist');
cy.contains('.form-section', 'default sender name recipients will see').should('exist');

cy.get('.info-chip').should('have.length.at.least', 3);
cy.contains('.field-label, .field small', 'Existing email address').should('exist');
cy.contains('.field-label, .field small', 'How did you hear about Runbox?').should('exist');

cy.contains('.form-actions button.submit', 'Set up my Runbox account').should('exist');
cy.contains('a', 'Use legacy signup page').should('not.exist');
});
});
12 changes: 12 additions & 0 deletions e2e/cypress/plugins/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,16 @@ module.exports = (on, config) => {
}
on('file:preprocessor', wp(options))
require('cypress-terminal-report/src/installLogsPrinter')(on);

// Force the browser into a specific timezone via TZ env var.
// Defaults to Europe/Oslo (CET/CEST) to expose bugs where
// account tz != browser tz. Override with env var CYPRESS_TZ.
on('before:browser:launch', (browser, launchOptions) => {
const tz = config.env.TZ || 'Europe/Oslo';
launchOptions.env = {
...(launchOptions.env || {}),
TZ: tz,
};
return launchOptions;
});
}
117 changes: 117 additions & 0 deletions e2e/cypress/support/ics-helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// --------- BEGIN RUNBOX LICENSE ---------
// Copyright (C) 2016-2026 Runbox Solutions AS (runbox.com).
//
// This file is part of Runbox 7.
//
// Runbox 7 is free software: You can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the
// Free Software Foundation, either version 3 of the License, or (at your
// option) any later version.
//
// Runbox 7 is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Runbox 7. If not, see <https://www.gnu.org/licenses/>.
// ---------- END RUNBOX LICENSE ----------

export function futureDateStr(daysFromNow: number): string {
const d = new Date();
d.setDate(d.getDate() + daysFromNow);
return d.toISOString().replace(/-/g, '').replace(/T.*/, '');
}

export function dateStrMonth(dateStr: string): number {
return parseInt(dateStr.substring(4, 6), 10);
}

export function dateStrYear(dateStr: string): number {
return parseInt(dateStr.substring(0, 4), 10);
}

export function dateStrDay(dateStr: string): number {
return parseInt(dateStr.substring(6, 8), 10);
}

export function buildIcs(veventBlocks: string[], vtimezone?: string): string {
const parts = [
'BEGIN:VCALENDAR',
'VERSION:2.0',
'PRODID:-//Runbox//E2E Test//EN',
];
if (vtimezone) { parts.push(vtimezone); }
parts.push(...veventBlocks, 'END:VCALENDAR');
return parts.join('\r\n');
}

export function dtLine(prefix: string, value: string): string {
return value.includes('=') ? `${prefix};${value}` : `${prefix}:${value}`;
}

export function makeVevent(dtstart: string, dtend: string, summary: string, uid: string, extra: string[] = []): string {
return [
'BEGIN:VEVENT',
dtLine('DTSTART', dtstart),
dtLine('DTEND', dtend),
`SUMMARY:${summary}`,
`UID:${uid}`,
'DTSTAMP:20260101T000000Z',
...extra,
'END:VEVENT',
].join('\r\n');
}

export const osloVtimezone = [
'BEGIN:VTIMEZONE',
'TZID:/citadel.org/20210210_1/Europe/Oslo',
'LAST-MODIFIED:20210210T123706Z',
'X-LIC-LOCATION:Europe/Oslo',
'BEGIN:STANDARD',
'TZNAME:CET',
'TZOFFSETFROM:+0200',
'TZOFFSETTO:+0100',
'DTSTART:19961027T030000',
'RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU',
'END:STANDARD',
'BEGIN:DAYLIGHT',
'TZNAME:CEST',
'TZOFFSETFROM:+0100',
'TZOFFSETTO:+0200',
'DTSTART:19810329T020000',
'RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU',
'END:DAYLIGHT',
'END:VTIMEZONE',
].join('\r\n');

/**
* Compute what the Angular date pipe would display for a given UTC time
* in the browser's current timezone. Returns 'HH:mm' format.
* dateStr: 'YYYYMMDD' format date string.
*/
export function expectedDisplayTime(dateStr: string, utcHour: number, utcMinute: number = 0): string {
const date = new Date(Date.UTC(dateStrYear(dateStr), dateStrMonth(dateStr) - 1, dateStrDay(dateStr), utcHour, utcMinute, 0));
return `${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`;
}

export const londonVtimezone = [
'BEGIN:VTIMEZONE',
'TZID:Europe/London',
'X-LIC-LOCATION:Europe/London',
'BEGIN:STANDARD',
'TZNAME:GMT',
'TZOFFSETFROM:+0100',
'TZOFFSETTO:+0000',
'DTSTART:19701025T020000',
'RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU',
'END:STANDARD',
'BEGIN:DAYLIGHT',
'TZNAME:BST',
'TZOFFSETFROM:+0000',
'TZOFFSETTO:+0100',
'DTSTART:19810329T010000',
'RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU',
'END:DAYLIGHT',
'END:VTIMEZONE',
].join('\r\n');
Loading
Loading