Files
accounted/lib/company/__tests__/home-domain.test.ts
T
MattssonandClaude Fable 5 99c94d467a fix(white-label): back-to-clients link points at the byra cockpit's home domain (#1973)
* fix(white-label): back-to-clients link points at the byra cockpit's home domain

A byra member working a company homed on another host (e.g. a pre-byra
company on canonical) got a relative /clients on the wrong host instead
of their white-label cockpit. resolveCockpitHref mirrors WL-14's home
rule: relative when the current host is the cockpit's home (brand domain,
or canonical for a brandless byra), else an absolute URL there. Cross-
host links render a plain <a> with a 'Hanteras via' hint; the hop lands
on the brand host's login (per-host sessions, WL-01) and WL-14 then
lands on /clients.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(white-label): show the cross-host cockpit hint as visible text

CodeRabbit: title-only hints never surface on touch devices, so the
expanded-sidebar and mobile external back-links now render the
'Hanteras via {domain}' line as small muted text under the label
(same pattern as the switcher's foreign entries). Also corrects the
comments claiming the no-company branch never renders the back-link:
it can, on its cockpit/settings surfaces, where the relative fallback
matches pre-change behavior.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-27 13:44:23 +02:00

278 lines
7.8 KiB
TypeScript

import { describe, it, expect } from 'vitest'
import {
partitionCompaniesByHomeDomain,
isCompanyHomedOnHost,
isCockpitLandingRole,
resolveLandingPath,
resolveCockpitHref,
type TeamBrandRef,
} from '../home-domain'
interface Entry {
id: string
teamId: string | null
}
const CANONICAL = {
canonicalDomain: 'app.gnubok.se',
canonicalAppName: 'Accounted',
}
const siffraBrand: TeamBrandRef = { domain: 'app.siffra.se', appName: 'Siffra' }
function partition(opts: {
companies: Entry[]
brandByTeam?: Map<string, TeamBrandRef>
hostBrandTeamId?: string | null
}) {
return partitionCompaniesByHomeDomain<Entry>({
companies: opts.companies,
getTeamId: (c) => c.teamId,
brandByTeam: opts.brandByTeam ?? new Map(),
hostBrandTeamId: opts.hostBrandTeamId ?? null,
...CANONICAL,
})
}
describe('partitionCompaniesByHomeDomain', () => {
it('canonical host with no brands anywhere: everything visible (additive guarantee)', () => {
const result = partition({
companies: [
{ id: 'own', teamId: 'personal-1' },
{ id: 'other', teamId: null },
],
})
expect(result.visible.map((c) => c.id)).toEqual(['own', 'other'])
expect(result.foreign).toHaveLength(0)
})
it('canonical host: branded companies become foreign under their brand domain', () => {
const result = partition({
companies: [
{ id: 'own-firma', teamId: 'personal-1' },
{ id: 'client', teamId: 'byra-1' },
],
brandByTeam: new Map([['byra-1', siffraBrand]]),
})
expect(result.visible.map((c) => c.id)).toEqual(['own-firma'])
expect(result.foreign).toEqual([
{ item: { id: 'client', teamId: 'byra-1' }, domain: 'app.siffra.se', appName: 'Siffra' },
])
})
it("brand host: only the brand team's companies are visible", () => {
const result = partition({
companies: [
{ id: 'own-firma', teamId: 'personal-1' },
{ id: 'client-a', teamId: 'byra-1' },
{ id: 'client-b', teamId: 'byra-1' },
],
brandByTeam: new Map([['byra-1', siffraBrand]]),
hostBrandTeamId: 'byra-1',
})
expect(result.visible.map((c) => c.id)).toEqual(['client-a', 'client-b'])
// The consultant's own firma points home to the canonical domain.
expect(result.foreign).toEqual([
{
item: { id: 'own-firma', teamId: 'personal-1' },
domain: 'app.gnubok.se',
appName: 'Accounted',
},
])
})
it('brand host: a company under ANOTHER brand points at that brand domain', () => {
const otherBrand: TeamBrandRef = { domain: 'app.other.se', appName: 'Other' }
const result = partition({
companies: [{ id: 'other-client', teamId: 'byra-2' }],
brandByTeam: new Map([
['byra-1', siffraBrand],
['byra-2', otherBrand],
]),
hostBrandTeamId: 'byra-1',
})
expect(result.visible).toHaveLength(0)
expect(result.foreign[0]!.domain).toBe('app.other.se')
})
})
describe('isCompanyHomedOnHost', () => {
const brandByTeam = new Map([['byra-1', siffraBrand]])
it('brandless company is homed on the canonical host', () => {
expect(
isCompanyHomedOnHost({
companyTeamId: 'personal-1',
brandByTeam,
hostBrandTeamId: null,
}),
).toBe(true)
})
it('branded company is NOT homed on the canonical host', () => {
expect(
isCompanyHomedOnHost({
companyTeamId: 'byra-1',
brandByTeam,
hostBrandTeamId: null,
}),
).toBe(false)
})
it('branded company is homed on its own brand host', () => {
expect(
isCompanyHomedOnHost({
companyTeamId: 'byra-1',
brandByTeam,
hostBrandTeamId: 'byra-1',
}),
).toBe(true)
})
it('brandless company is NOT homed on a brand host', () => {
expect(
isCompanyHomedOnHost({
companyTeamId: 'personal-1',
brandByTeam,
hostBrandTeamId: 'byra-1',
}),
).toBe(false)
})
})
describe('isCockpitLandingRole', () => {
it('owner gets the automatic cockpit landing', () => {
expect(isCockpitLandingRole('owner')).toBe(true)
})
it('admin gets the automatic cockpit landing', () => {
expect(isCockpitLandingRole('admin')).toBe(true)
})
it('plain member lands like a regular user', () => {
expect(isCockpitLandingRole('member')).toBe(false)
})
it('unknown roles default to the regular landing (allowlist)', () => {
expect(isCockpitLandingRole('viewer')).toBe(false)
expect(isCockpitLandingRole('')).toBe(false)
})
})
describe('resolveLandingPath', () => {
it("byrå staff on their brand host land in the cockpit", () => {
expect(
resolveLandingPath({
hostBrandTeamId: 'byra-1',
byraTeams: [{ teamId: 'byra-1', hasBrand: true }],
}),
).toBe('/clients')
})
it("someone else's brand host never lands in the cockpit", () => {
expect(
resolveLandingPath({
hostBrandTeamId: 'byra-2',
byraTeams: [{ teamId: 'byra-1', hasBrand: true }],
}),
).toBe('/')
})
it('brandless byrå homes its cockpit on the canonical host (WL-01)', () => {
expect(
resolveLandingPath({
hostBrandTeamId: null,
byraTeams: [{ teamId: 'byra-1', hasBrand: false }],
}),
).toBe('/clients')
})
it('a BRANDED byrå does not land in the cockpit on the canonical host', () => {
expect(
resolveLandingPath({
hostBrandTeamId: null,
byraTeams: [{ teamId: 'byra-1', hasBrand: true }],
}),
).toBe('/')
})
it('non-byrå users always land on / (byte-identical flow)', () => {
expect(resolveLandingPath({ hostBrandTeamId: null, byraTeams: [] })).toBe('/')
expect(resolveLandingPath({ hostBrandTeamId: 'byra-1', byraTeams: [] })).toBe('/')
})
})
describe('resolveCockpitHref', () => {
const brandByTeam = new Map([['byra-1', siffraBrand]])
const canonicalDomain = CANONICAL.canonicalDomain
it('brandless byrå on the canonical host stays relative', () => {
expect(
resolveCockpitHref({
byraTeamId: 'byra-1',
brandByTeam: new Map(),
hostBrandTeamId: null,
canonicalDomain,
}),
).toBe('/clients')
})
it('branded byrå on its own brand host stays relative', () => {
expect(
resolveCockpitHref({
byraTeamId: 'byra-1',
brandByTeam,
hostBrandTeamId: 'byra-1',
canonicalDomain,
}),
).toBe('/clients')
})
it('branded byrå on the canonical host points at the brand domain (problem 4)', () => {
expect(
resolveCockpitHref({
byraTeamId: 'byra-1',
brandByTeam,
hostBrandTeamId: null,
canonicalDomain,
}),
).toBe('https://app.siffra.se/clients')
})
it("branded byrå on someone ELSE's brand host points at its own brand domain", () => {
expect(
resolveCockpitHref({
byraTeamId: 'byra-1',
brandByTeam,
hostBrandTeamId: 'byra-2',
canonicalDomain,
}),
).toBe('https://app.siffra.se/clients')
})
it('brandless byrå on a foreign brand host points home to canonical (WL-14 symmetry)', () => {
expect(
resolveCockpitHref({
byraTeamId: 'byra-1',
brandByTeam: new Map(),
hostBrandTeamId: 'byra-2',
canonicalDomain,
}),
).toBe('https://app.gnubok.se/clients')
})
it('byrå with zero client companies still resolves via its own brand entry', () => {
// The layout adds the byrå team id to the resolveBrandsForTeams id list
// precisely so this map entry exists even when no membership company
// belongs to the byrå (pre-byrå companies have team_id null).
expect(
resolveCockpitHref({
byraTeamId: 'byra-1',
brandByTeam: new Map([['byra-1', siffraBrand]]),
hostBrandTeamId: null,
canonicalDomain,
}),
).toBe('https://app.siffra.se/clients')
})
})