mirror of
https://github.com/HeyPuter/puter.git
synced 2026-11-13 20:12:57 -10:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6b60f7da9c |
No files matched your search
@@ -340,7 +340,12 @@ el.open();
|
||||
The account button and Puter's app browser for a first-party app on a Puter subdomain (`notes.puter.com`). It is internal and left out of the public docs, because the app browser only works one subdomain under the Puter origin. It follows the page's own puter.js sign-in, never the Puter session's.
|
||||
|
||||
- Signed out, it shows **Sign in**, which calls `puter.auth.signIn()`. A temporary account counts as signed out.
|
||||
- Signed in, it shows the user's profile picture, or the username's initial when there is none. Clicking it opens a menu with the username and **Sign out**, which calls `puter.auth.signOut()`.
|
||||
- Signed in, it shows the user's profile picture, or the username's initial when there is none. Clicking it opens a menu with:
|
||||
- The user's picture, username and email. The email line is hidden when whoami leaves it out, such as for an app that can't read it.
|
||||
- **Manage account**, **Usage** and **Open Puter**, links that open the GUI origin's `/dashboard/#account`, `/dashboard/#usage` and `/` in a new tab.
|
||||
- **Send feedback**, which calls `puter.ui.showFeedbackDialog()`.
|
||||
- **Sign out**, which calls `puter.auth.signOut()`.
|
||||
- The menu opens with focus on its first item. ArrowUp and ArrowDown move between items and wrap around, and Home and End jump to the first and last.
|
||||
- On a page exactly one label under the Puter origin, with the same scheme and port, an **Apps** button opens the app browser (`/embed/apps`) in a panel under the button. It names the signed-in user as `#user=<uuid>`, so the app browser shows the Puter session's apps only to that user (see `doc/embedding-app-browser.md`), and passes a forced `theme` on as `theme=`. Elsewhere the button is hidden, because the server refuses the framing.
|
||||
- The menu and the Apps panel hang under their button with a caret pointing at it, and close on Escape, a press outside them, or their button. They're in the browser's top layer where it has one, so a host's `transform`, `overflow` or `z-index` can't clip or cover them. On screens up to 600px wide, the Apps panel spans the width below the button.
|
||||
- Closing the Apps panel keeps the app browser, so reopening it keeps its scroll and search. Any change to the page's sign-in discards it, so it never stays loaded for the previous user.
|
||||
@@ -355,6 +360,10 @@ The account button and Puter's app browser for a first-party app on a Puter subd
|
||||
| `sign-out-label` | `Sign out` | Sign-out menu item text |
|
||||
| `close-label` | `Close` | The Apps panel's close button label |
|
||||
| `account-label` | `Account` | Account button label, read as `Account: <username>` |
|
||||
| `manage-account-label` | `Manage account` | Menu link to the account settings |
|
||||
| `usage-label` | `Usage` | Menu link to the usage page |
|
||||
| `open-puter-label` | `Open Puter` | Menu link to Puter |
|
||||
| `feedback-label` | `Send feedback` | Menu item that opens the feedback dialog |
|
||||
| `theme` | follows the OS | `light` or `dark`, for the component and the app browser in it |
|
||||
|
||||
Changing an attribute updates the component in place.
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
*
|
||||
* Signed out (a temporary account counts as signed out), it shows a Sign in
|
||||
* button. Signed in, it shows the user's profile picture (or initial), which
|
||||
* opens a menu with the username and Sign out. On a page one subdomain under
|
||||
* opens a menu with the account, links to manage it, see its usage and open
|
||||
* Puter, Send feedback, and Sign out. On a page one subdomain under
|
||||
* the Puter origin, an Apps button opens Puter's app browser in a panel under
|
||||
* it, for the same user: signed out, or signed in as an account other than
|
||||
* the Puter session's, the browser shows only recommended apps. The browser
|
||||
@@ -12,7 +13,8 @@
|
||||
* Renders nothing inside the Puter desktop.
|
||||
*
|
||||
* Attributes: theme, apps-label, sign-in-label, sign-out-label, close-label,
|
||||
* account-label
|
||||
* account-label, manage-account-label, usage-label, open-puter-label,
|
||||
* feedback-label
|
||||
* Parts: apps-button, avatar, sign-in, menu, apps-panel
|
||||
* Events: signin (detail.user), signout
|
||||
*/
|
||||
@@ -24,6 +26,7 @@ import { canFrameGuiEmbed } from '../../lib/guiEmbed.js';
|
||||
const APPS_ICON = `<svg viewBox="0 0 24 24" width="20" height="20" aria-hidden="true" focusable="false">${
|
||||
[5, 12, 19].flatMap(y => [5, 12, 19].map(x => `<circle cx="${x}" cy="${y}" r="2"/>`)).join('')
|
||||
}</svg>`;
|
||||
const EXTERNAL_ICON = '<svg class="menu-external" viewBox="0 0 32 32" width="11" height="11" aria-hidden="true" focusable="false"><path d="m26 28h-20a2 2 0 0 1 -2-2v-20a2 2 0 0 1 2-2h10v2h-10v20h20v-10h2v10a2 2 0 0 1 -2 2z"/><path d="m20 2v2h6.586l-8.586 8.586 1.414 1.414 8.586-8.586v6.586h2v-10z"/></svg>';
|
||||
const CLOSE_ICON = '<svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true" focusable="false" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M6 6l12 12M18 6L6 18"/></svg>';
|
||||
|
||||
const LABELS = {
|
||||
@@ -32,8 +35,19 @@ const LABELS = {
|
||||
'sign-out-label': 'Sign out',
|
||||
'close-label': 'Close',
|
||||
'account-label': 'Account',
|
||||
'manage-account-label': 'Manage account',
|
||||
'usage-label': 'Usage',
|
||||
'open-puter-label': 'Open Puter',
|
||||
'feedback-label': 'Send feedback',
|
||||
};
|
||||
|
||||
// Menu links, each opening a page of the Puter GUI in a new tab.
|
||||
const MENU_LINKS = [
|
||||
{ className: 'manage-account', label: 'manage-account-label', path: '/dashboard/#account' },
|
||||
{ className: 'usage', label: 'usage-label', path: '/dashboard/#usage' },
|
||||
{ className: 'open-puter', label: 'open-puter-label', path: '/' },
|
||||
];
|
||||
|
||||
// Gap between a panel and the viewport edge, and between its caret and corners.
|
||||
const EDGE = 8;
|
||||
const CARET_INSET = 20;
|
||||
@@ -216,7 +230,8 @@ class PuterUserMenu extends PuterWebComponent {
|
||||
text-align: center;
|
||||
}
|
||||
.avatar[aria-expanded="true"] { box-shadow: 0 0 0 3px rgba(8, 142, 240, .25); }
|
||||
.avatar img {
|
||||
.avatar img,
|
||||
.menu-avatar img {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
@@ -285,31 +300,78 @@ class PuterUserMenu extends PuterWebComponent {
|
||||
}
|
||||
|
||||
.menu {
|
||||
min-width: 200px;
|
||||
min-width: 220px;
|
||||
max-width: min(300px, calc(100vw - ${EDGE * 2}px));
|
||||
padding: 4px 0;
|
||||
text-align: left;
|
||||
}
|
||||
.menu-user {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 8px 14px 10px;
|
||||
margin-bottom: 4px;
|
||||
border-bottom: 1px solid #eee;
|
||||
color: #414650;
|
||||
font-size: 14px;
|
||||
}
|
||||
.menu-avatar {
|
||||
position: relative;
|
||||
flex: none;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
overflow: hidden;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(#34a5f8, #088ef0);
|
||||
color: #fff;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
line-height: 40px;
|
||||
text-align: center;
|
||||
}
|
||||
.menu-identity {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
}
|
||||
.menu-username,
|
||||
.menu-email {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.menu-username {
|
||||
color: #414650;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.menu-email {
|
||||
color: #6b7280;
|
||||
font-size: 12px;
|
||||
}
|
||||
.menu-item {
|
||||
display: block;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
padding: 8px 14px;
|
||||
border: none;
|
||||
background: none;
|
||||
color: #333;
|
||||
font-size: 14px;
|
||||
line-height: normal;
|
||||
text-align: left;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
.menu-external {
|
||||
flex: none;
|
||||
fill: currentColor;
|
||||
opacity: .7;
|
||||
}
|
||||
.menu-separator {
|
||||
height: 1px;
|
||||
margin: 4px 0;
|
||||
background-color: #eee;
|
||||
}
|
||||
.menu-item:focus-visible {
|
||||
outline: none;
|
||||
@@ -411,7 +473,9 @@ class PuterUserMenu extends PuterWebComponent {
|
||||
}
|
||||
:host(.puter-theme-dark) .menu-user,
|
||||
:host(.puter-theme-dark) .apps-header { border-color: #3a3a3a; }
|
||||
:host(.puter-theme-dark) .menu-user,
|
||||
:host(.puter-theme-dark) .menu-separator { background-color: #3a3a3a; }
|
||||
:host(.puter-theme-dark) .menu-email { color: #a0a0a0; }
|
||||
:host(.puter-theme-dark) .menu-username,
|
||||
:host(.puter-theme-dark) .apps-title,
|
||||
:host(.puter-theme-dark) .menu-item { color: #e6e6e6; }
|
||||
:host(.puter-theme-dark) .menu-item:focus-visible { background-color: #3a3a3a; color: #fff; }
|
||||
@@ -442,7 +506,16 @@ class PuterUserMenu extends PuterWebComponent {
|
||||
<button type="button" class="avatar" part="avatar" aria-haspopup="menu" aria-expanded="false" hidden></button>
|
||||
</div>
|
||||
<div class="panel menu" part="menu" role="menu" tabindex="-1" hidden>
|
||||
<div class="menu-user" hidden></div>
|
||||
<div class="menu-user" hidden>
|
||||
<span class="menu-avatar" aria-hidden="true"></span>
|
||||
<span class="menu-identity">
|
||||
<span class="menu-username"></span>
|
||||
<span class="menu-email" hidden></span>
|
||||
</span>
|
||||
</div>
|
||||
${MENU_LINKS.map(link => `<a class="menu-item ${link.className}" role="menuitem" target="_blank" rel="noopener"><span class="menu-label"></span>${EXTERNAL_ICON}</a>`).join('')}
|
||||
<button type="button" class="menu-item feedback" role="menuitem"></button>
|
||||
<div class="menu-separator" role="separator"></div>
|
||||
<button type="button" class="menu-item sign-out" role="menuitem"></button>
|
||||
</div>
|
||||
<div class="panel apps-panel" part="apps-panel" role="dialog" tabindex="-1" hidden>
|
||||
@@ -471,6 +544,10 @@ class PuterUserMenu extends PuterWebComponent {
|
||||
if ( this.$('.menu').hidden ) this._openMenu();
|
||||
else this._closePanels();
|
||||
});
|
||||
for ( const link of MENU_LINKS ) {
|
||||
this.$(`.${link.className}`).addEventListener('click', () => this._closePanels());
|
||||
}
|
||||
this.$('.feedback').addEventListener('click', () => this._sendFeedback());
|
||||
this.$('.sign-out').addEventListener('click', () => this._signOut());
|
||||
this.$('.apps-close').addEventListener('click', () => this._closePanels({ returnFocus: true }));
|
||||
this.shadowRoot.addEventListener('keydown', (event) => {
|
||||
@@ -482,6 +559,8 @@ class PuterUserMenu extends PuterWebComponent {
|
||||
} else if ( event.key === 'Tab' && open.classList.contains('menu') ) {
|
||||
// Tab leaves the menu: it closes, and focus moves on from its button.
|
||||
this._closePanels({ returnFocus: true });
|
||||
} else if ( open.classList.contains('menu') && this._moveMenuFocus(event.key) ) {
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -501,6 +580,10 @@ class PuterUserMenu extends PuterWebComponent {
|
||||
close.title = this._label('close-label');
|
||||
this.$('.sign-in').textContent = this._label('sign-in-label');
|
||||
this.$('.sign-out').textContent = this._label('sign-out-label');
|
||||
this.$('.feedback').textContent = this._label('feedback-label');
|
||||
for ( const link of MENU_LINKS ) {
|
||||
this.$(`.${link.className} .menu-label`).textContent = this._label(link.label);
|
||||
}
|
||||
this._renderAvatarLabel();
|
||||
}
|
||||
|
||||
@@ -522,19 +605,17 @@ class PuterUserMenu extends PuterWebComponent {
|
||||
|
||||
const avatar = this.$('.avatar');
|
||||
avatar.hidden = !this.#ready || !signedIn;
|
||||
avatar.textContent = username ? username[0].toUpperCase() : '?';
|
||||
if ( this.#picture ) {
|
||||
const img = document.createElement('img');
|
||||
img.alt = '';
|
||||
// The initial stays underneath for a picture the page's CSP refuses.
|
||||
img.addEventListener('error', () => img.remove());
|
||||
img.src = this.#picture;
|
||||
avatar.append(img);
|
||||
}
|
||||
this._fillAvatar(avatar, username);
|
||||
this._renderAvatarLabel();
|
||||
const menuUser = this.$('.menu-user');
|
||||
menuUser.textContent = username;
|
||||
menuUser.hidden = !username;
|
||||
this.$('.menu-user').hidden = !username;
|
||||
this.$('.menu-username').textContent = username;
|
||||
this._fillAvatar(this.$('.menu-avatar'), username);
|
||||
const email = typeof this.#user?.email === 'string' ? this.#user.email : '';
|
||||
this.$('.menu-email').textContent = email;
|
||||
this.$('.menu-email').hidden = !email;
|
||||
for ( const link of MENU_LINKS ) {
|
||||
this.$(`.${link.className}`).href = new URL(link.path, globalThis.puter.defaultGUIOrigin).href;
|
||||
}
|
||||
if ( avatar.hidden && !this.$('.menu').hidden ) this._closePanels();
|
||||
if ( this.#ready && this.#focusSignIn ) {
|
||||
this.#focusSignIn = false;
|
||||
@@ -542,6 +623,18 @@ class PuterUserMenu extends PuterWebComponent {
|
||||
}
|
||||
}
|
||||
|
||||
/** The username's initial, with the profile picture over it once there is one. */
|
||||
_fillAvatar (el, username) {
|
||||
el.textContent = username ? username[0].toUpperCase() : '?';
|
||||
if ( ! this.#picture ) return;
|
||||
const img = document.createElement('img');
|
||||
img.alt = '';
|
||||
// The initial stays underneath for a picture the page's CSP refuses.
|
||||
img.addEventListener('error', () => img.remove());
|
||||
img.src = this.#picture;
|
||||
el.append(img);
|
||||
}
|
||||
|
||||
// -- Panels --
|
||||
|
||||
_openPanel () {
|
||||
@@ -607,7 +700,23 @@ class PuterUserMenu extends PuterWebComponent {
|
||||
_openMenu () {
|
||||
const menu = this.$('.menu');
|
||||
this._showPanel(menu);
|
||||
this.$('.sign-out').focus({ preventScroll: true });
|
||||
this.$('.menu-item').focus({ preventScroll: true });
|
||||
}
|
||||
|
||||
/** Moves focus between menu items for an arrow, Home or End key; false for any other key. */
|
||||
_moveMenuFocus (key) {
|
||||
const items = [...this.$$('.menu .menu-item')];
|
||||
const current = items.indexOf(this.shadowRoot.activeElement);
|
||||
const last = items.length - 1;
|
||||
const next = {
|
||||
ArrowDown: current < 0 || current === last ? 0 : current + 1,
|
||||
ArrowUp: current <= 0 ? last : current - 1,
|
||||
Home: 0,
|
||||
End: last,
|
||||
}[key];
|
||||
if ( next === undefined ) return false;
|
||||
items[next].focus({ preventScroll: true });
|
||||
return true;
|
||||
}
|
||||
|
||||
_openApps () {
|
||||
@@ -671,6 +780,11 @@ class PuterUserMenu extends PuterWebComponent {
|
||||
);
|
||||
}
|
||||
|
||||
_sendFeedback () {
|
||||
this._closePanels();
|
||||
void globalThis.puter.ui.showFeedbackDialog();
|
||||
}
|
||||
|
||||
async _signOut () {
|
||||
this.#focusSignIn = Boolean(this.shadowRoot.activeElement);
|
||||
this._closePanels();
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import PuterUserMenu from './PuterUserMenu.js';
|
||||
|
||||
const USER = { username: 'jelveh', uuid: '0b1c9a52-3f0e-4c7a-9a65-2f6d1e8b7c40' };
|
||||
const USER = { username: 'jelveh', uuid: '0b1c9a52-3f0e-4c7a-9a65-2f6d1e8b7c40', email: '[email protected]' };
|
||||
const OTHER = { username: 'sam', uuid: '7d2e4f10-8a3b-4c5d-9e6f-0a1b2c3d4e5f' };
|
||||
const TEMP = { username: 'quiet_otter_42', uuid: '5c4b3a29-1807-4f6e-9d5c-4b3a29180716', is_temp: true };
|
||||
const PICTURE = 'data:image/png;base64,iVBORw0KGgo=';
|
||||
@@ -24,6 +24,9 @@ const fakePuter = ({ token = null, user = null, picture = null } = {}) => {
|
||||
listeners.add(listener);
|
||||
return () => listeners.delete(listener);
|
||||
},
|
||||
ui: {
|
||||
showFeedbackDialog: vi.fn(() => new Promise(() => {})),
|
||||
},
|
||||
auth: {
|
||||
getProfilePicture: vi.fn(async () => currentPicture),
|
||||
signIn: vi.fn(() => new Promise(() => {})),
|
||||
@@ -164,7 +167,10 @@ describe('<puter-user-menu>', () => {
|
||||
part(el, '.avatar').click();
|
||||
expect(shown(el, '.menu')).toBe(true);
|
||||
expect(part(el, '.avatar').getAttribute('aria-expanded')).toBe('true');
|
||||
expect(part(el, '.menu-user').textContent).toBe('jelveh');
|
||||
expect(part(el, '.menu-username').textContent).toBe('jelveh');
|
||||
expect(part(el, '.menu-email').textContent).toBe(USER.email);
|
||||
expect(shown(el, '.menu-email')).toBe(true);
|
||||
expect(part(el, '.menu-avatar').textContent).toBe('J');
|
||||
|
||||
part(el, '.apps-button').click();
|
||||
expect(shown(el, '.menu')).toBe(false);
|
||||
@@ -173,6 +179,80 @@ describe('<puter-user-menu>', () => {
|
||||
expect(frameOf(el).src).toBe(`https://puter.com/embed/apps#user=${USER.uuid}`);
|
||||
});
|
||||
|
||||
it('links to the account, its usage and Puter, each in a new tab', async () => {
|
||||
vi.stubGlobal('puter', fakePuter({ token: 'app-token', user: USER }));
|
||||
const el = await mount();
|
||||
part(el, '.avatar').click();
|
||||
expect(el.shadowRoot.activeElement).toBe(part(el, '.manage-account'));
|
||||
const links = [...el.shadowRoot.querySelectorAll('.menu a.menu-item')].map(link => ({
|
||||
label: link.textContent,
|
||||
href: link.href,
|
||||
target: link.target,
|
||||
rel: link.rel,
|
||||
}));
|
||||
expect(links).toEqual([
|
||||
{ label: 'Manage account', href: 'https://puter.com/dashboard/#account', target: '_blank', rel: 'noopener' },
|
||||
{ label: 'Usage', href: 'https://puter.com/dashboard/#usage', target: '_blank', rel: 'noopener' },
|
||||
{ label: 'Open Puter', href: 'https://puter.com/', target: '_blank', rel: 'noopener' },
|
||||
]);
|
||||
|
||||
part(el, '.usage').dispatchEvent(new MouseEvent('click', { cancelable: true }));
|
||||
expect(shown(el, '.menu')).toBe(false);
|
||||
});
|
||||
|
||||
it('hides the email line when the account has none to show', async () => {
|
||||
vi.stubGlobal('puter', fakePuter({ token: 'app-token', user: OTHER }));
|
||||
const el = await mount();
|
||||
part(el, '.avatar').click();
|
||||
expect(part(el, '.menu-username').textContent).toBe('sam');
|
||||
expect(shown(el, '.menu-email')).toBe(false);
|
||||
});
|
||||
|
||||
it('shows the profile picture in the menu header too', async () => {
|
||||
vi.stubGlobal('puter', fakePuter({ token: 'app-token', user: USER, picture: PICTURE }));
|
||||
const el = await mount();
|
||||
expect(part(el, '.menu-avatar img').getAttribute('src')).toBe(PICTURE);
|
||||
expect(part(el, '.menu-avatar').textContent).toBe('J');
|
||||
});
|
||||
|
||||
it('opens the feedback dialog from the menu', async () => {
|
||||
const sdk = fakePuter({ token: 'app-token', user: USER });
|
||||
vi.stubGlobal('puter', sdk);
|
||||
const el = await mount();
|
||||
part(el, '.avatar').click();
|
||||
part(el, '.feedback').click();
|
||||
expect(sdk.ui.showFeedbackDialog).toHaveBeenCalledTimes(1);
|
||||
expect(shown(el, '.menu')).toBe(false);
|
||||
});
|
||||
|
||||
it('moves focus through the menu with the arrow, Home and End keys', async () => {
|
||||
vi.stubGlobal('puter', fakePuter({ token: 'app-token', user: USER }));
|
||||
const el = await mount();
|
||||
part(el, '.avatar').click();
|
||||
const press = (key) => {
|
||||
const event = new KeyboardEvent('keydown', { key, bubbles: true, composed: true, cancelable: true });
|
||||
el.shadowRoot.activeElement.dispatchEvent(event);
|
||||
return event;
|
||||
};
|
||||
const focused = () => el.shadowRoot.activeElement.classList[1];
|
||||
|
||||
expect(focused()).toBe('manage-account');
|
||||
expect(press('ArrowDown').defaultPrevented).toBe(true);
|
||||
expect(focused()).toBe('usage');
|
||||
press('End');
|
||||
expect(focused()).toBe('sign-out');
|
||||
press('ArrowDown');
|
||||
expect(focused()).toBe('manage-account');
|
||||
press('ArrowUp');
|
||||
expect(focused()).toBe('sign-out');
|
||||
press('ArrowUp');
|
||||
expect(focused()).toBe('feedback');
|
||||
press('Home');
|
||||
expect(focused()).toBe('manage-account');
|
||||
expect(press('a').defaultPrevented).toBe(false);
|
||||
expect(shown(el, '.menu')).toBe(true);
|
||||
});
|
||||
|
||||
it('hangs the panel under its button with the caret pointing at it', async () => {
|
||||
vi.stubGlobal('puter', fakePuter({ token: 'app-token', user: USER }));
|
||||
const el = await mount();
|
||||
@@ -381,12 +461,25 @@ describe('<puter-user-menu>', () => {
|
||||
|
||||
it('uses label attributes, including ones changed later', async () => {
|
||||
vi.stubGlobal('puter', fakePuter({ token: 'app-token', user: USER }));
|
||||
const el = await mount({ 'apps-label': 'Applis', 'sign-out-label': 'Déconnexion', 'account-label': 'Compte' });
|
||||
const el = await mount({
|
||||
'apps-label': 'Applis',
|
||||
'sign-out-label': 'Déconnexion',
|
||||
'account-label': 'Compte',
|
||||
'manage-account-label': 'Gérer le compte',
|
||||
'usage-label': 'Utilisation',
|
||||
'open-puter-label': 'Ouvrir Puter',
|
||||
'feedback-label': 'Envoyer un avis',
|
||||
});
|
||||
expect(part(el, '.apps-button').getAttribute('aria-label')).toBe('Applis');
|
||||
expect(part(el, '.apps-title').textContent).toBe('Applis');
|
||||
expect(part(el, '.apps-panel').getAttribute('aria-label')).toBe('Applis');
|
||||
expect(part(el, '.sign-out').textContent).toBe('Déconnexion');
|
||||
expect(part(el, '.avatar').getAttribute('aria-label')).toBe('Compte: jelveh');
|
||||
expect(part(el, '.manage-account').textContent).toBe('Gérer le compte');
|
||||
expect(part(el, '.usage').textContent).toBe('Utilisation');
|
||||
expect(part(el, '.open-puter').textContent).toBe('Ouvrir Puter');
|
||||
expect(part(el, '.feedback').textContent).toBe('Envoyer un avis');
|
||||
expect(part(el, '.manage-account .menu-external')).not.toBeNull();
|
||||
|
||||
el.setAttribute('close-label', 'Fermer');
|
||||
el.setAttribute('sign-in-label', 'Connexion');
|
||||
|
||||
Reference in new issue
Block a user