@csntgao/uni-base 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@csntgao/uni-base",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "type": "module",
5
5
  "packageManager": "pnpm@11.3.0",
6
6
  "publishConfig": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uniplat/ai-employee-web-components",
3
- "version": "0.23.4",
3
+ "version": "0.23.5",
4
4
  "description": "Framework-independent Web Components for Uniplat account, login, application switching and AI Employee chat",
5
5
  "type": "module",
6
6
  "sideEffects": true,
@@ -84,6 +84,8 @@ interface UserLoginBranding {
84
84
 
85
85
  type QrViewState = 'idle' | 'loading' | UserLoginQrStatus | 'error'
86
86
 
87
+ export const USER_LOGIN_QR_LOGIN_ENABLED = false
88
+
87
89
  const EMPTY_ERRORS: FormErrors = {
88
90
  mobile: '',
89
91
  smsCode: '',
@@ -1063,9 +1065,12 @@ export class UniplatUserLogin extends LitElement {
1063
1065
  }
1064
1066
 
1065
1067
  private renderTabs() {
1068
+ const visibleMethods = LOGIN_METHODS.filter(
1069
+ ({ method }) => method !== 'qr-code' || USER_LOGIN_QR_LOGIN_ENABLED,
1070
+ )
1066
1071
  return html`
1067
1072
  <div class="tabs" part="title tabs" role="tablist" aria-label="登录方式">
1068
- ${LOGIN_METHODS.map(({ method, label }) => {
1073
+ ${visibleMethods.map(({ method, label }) => {
1069
1074
  const available = this.isMethodAvailable(method)
1070
1075
  return html`
1071
1076
  <button
@@ -1426,6 +1431,7 @@ export class UniplatUserLogin extends LitElement {
1426
1431
  const transport = this.config?.transport
1427
1432
  if (method === 'verification-code') return Boolean(transport)
1428
1433
  if (method === 'password') return typeof transport?.loginWithPassword === 'function'
1434
+ if (!USER_LOGIN_QR_LOGIN_ENABLED) return false
1429
1435
  return (
1430
1436
  typeof transport?.createQrChallenge === 'function' &&
1431
1437
  typeof transport.pollQrChallenge === 'function'
@@ -8,6 +8,7 @@ import {
8
8
  type UserLoginErrorDetail,
9
9
  type UserLoginTransport,
10
10
  } from '../src'
11
+ import { USER_LOGIN_QR_LOGIN_ENABLED } from '../src/user-login'
11
12
 
12
13
  async function settle(login: UniplatUserLogin): Promise<void> {
13
14
  await Promise.resolve()
@@ -90,13 +91,15 @@ describe('<uniplat-user-login>', () => {
90
91
  /\.security-icon::before\s*\{[^}]*left:\s*50%;[^}]*box-sizing:\s*border-box;[^}]*transform:\s*translateX\(-50%\);/s,
91
92
  )
92
93
  expect(login.shadowRoot?.textContent).toContain('验证码登录')
93
- expect(login.shadowRoot?.querySelectorAll('[role="tab"]')).toHaveLength(3)
94
+ expect(USER_LOGIN_QR_LOGIN_ENABLED).toBe(false)
95
+ expect(login.shadowRoot?.querySelectorAll('[role="tab"]')).toHaveLength(2)
96
+ expect(login.shadowRoot?.textContent).not.toContain('扫码登录')
94
97
  expect(
95
98
  login.shadowRoot?.querySelector<HTMLButtonElement>('[role="tab"]:first-child')?.disabled,
96
99
  ).toBe(true)
97
100
  expect(
98
101
  login.shadowRoot?.querySelector<HTMLButtonElement>('[role="tab"]:last-child')?.disabled,
99
- ).toBe(true)
102
+ ).toBe(false)
100
103
  expect(login.shadowRoot?.querySelector('#uniplat-login-image-code')).toBeNull()
101
104
  expect(login.shadowRoot?.querySelector('.captcha-image')).toBeNull()
102
105
  expect(login.shadowRoot?.textContent).not.toContain('图形验证码')
@@ -506,94 +509,125 @@ describe('<uniplat-user-login>', () => {
506
509
  expect(passwordInput?.value).toBe('')
507
510
  })
508
511
 
509
- it('keeps QR status in one panel and clears polling after confirmation', async () => {
510
- vi.useFakeTimers()
512
+ it('hides QR login and falls back to verification login while the feature is disabled', async () => {
511
513
  const createQrChallenge = vi.fn(async () => ({
512
- challengeId: 'obviously-fake-qr-challenge',
513
- imageUrl:
514
- 'data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" width="120" height="120"%3E%3Crect width="120" height="120"/%3E%3C/svg%3E',
515
- pollIntervalMs: 750,
514
+ challengeId: 'obviously-fake-hidden-challenge',
515
+ imageUrl: 'data:image/png;base64,b2J2aW91c2x5LWZha2U=',
516
516
  }))
517
- const pollQrChallenge = vi
518
- .fn()
519
- .mockResolvedValueOnce({ status: 'scanned' })
520
- .mockResolvedValueOnce({
521
- status: 'confirmed',
522
- result: { applicationName: 'HrSaas' },
523
- })
524
- const transport = createTransport({ createQrChallenge, pollQrChallenge })
517
+ const pollQrChallenge = vi.fn(async () => ({ status: 'pending' as const }))
525
518
  const login = document.createElement(USER_LOGIN_TAG)
526
- const loginEvents: unknown[] = []
527
- login.addEventListener('login-success', (event) => {
528
- loginEvents.push((event as CustomEvent).detail)
529
- })
530
519
  login.configure({
531
- transport,
520
+ transport: createTransport({ createQrChallenge, pollQrChallenge }),
532
521
  applicationName: 'HrSaas',
533
522
  initialMethod: 'qr-code',
534
- qrPollIntervalMs: 500,
535
523
  })
536
524
  document.body.append(login)
537
525
  await settle(login)
538
526
 
539
- expect(createQrChallenge).toHaveBeenCalledWith({ applicationName: 'HrSaas' })
540
- expect(login.shadowRoot?.querySelector<HTMLImageElement>('.qr-image')?.src).toContain(
541
- 'data:image/svg+xml',
542
- )
543
- expect(login.shadowRoot?.textContent).toContain('请使用微信扫描小程序码登录')
544
- expect(login.shadowRoot?.textContent).toContain('未登录时先完成登录')
545
- expect(login.shadowRoot?.textContent).toContain('电脑端同步登录')
546
- expect(vi.getTimerCount()).toBe(1)
547
- expect(login.shadowRoot?.innerHTML).not.toContain('obviously-fake-qr-challenge')
548
-
549
- await vi.advanceTimersByTimeAsync(500)
527
+ expect(login.shadowRoot?.querySelectorAll('[role="tab"]')).toHaveLength(2)
528
+ expect(login.shadowRoot?.textContent).not.toContain('扫码登录')
529
+ expect(login.shadowRoot?.querySelector('.qr-panel')).toBeNull()
530
+ expect(
531
+ login.shadowRoot?.querySelector('[role="tab"][aria-selected="true"]')?.textContent,
532
+ ).toContain('验证码登录')
533
+ expect(createQrChallenge).not.toHaveBeenCalled()
550
534
  expect(pollQrChallenge).not.toHaveBeenCalled()
551
- await vi.advanceTimersByTimeAsync(250)
552
- await settle(login)
553
- expect(login.shadowRoot?.textContent).toContain('扫码成功,请在手机上确认')
554
- expect(vi.getTimerCount()).toBe(1)
555
- expect(pollQrChallenge).toHaveBeenCalledWith('obviously-fake-qr-challenge', 'HrSaas')
556
-
557
- await vi.advanceTimersByTimeAsync(750)
558
- await settle(login)
559
- expect(loginEvents).toEqual([{ applicationName: 'HrSaas' }])
560
- expect(JSON.stringify(loginEvents)).not.toContain('obviously-fake-qr-challenge')
561
- expect(vi.getTimerCount()).toBe(0)
562
535
  })
563
536
 
564
- it('stops polling and offers a refresh after QR login is rejected', async () => {
565
- vi.useFakeTimers()
566
- const transport = createTransport({
567
- createQrChallenge: vi.fn(async () => ({
568
- challengeId: 'obviously-fake-rejected-challenge',
569
- imageUrl: 'data:image/png;base64,b2J2aW91c2x5LWZha2UtcG5n',
570
- pollIntervalMs: 500,
571
- })),
572
- pollQrChallenge: vi.fn(async () => ({ status: 'rejected' as const })),
573
- })
574
- const errors: UserLoginErrorDetail[] = []
575
- const login = document.createElement(USER_LOGIN_TAG)
576
- login.addEventListener('error', (event) => {
577
- errors.push((event as unknown as CustomEvent<UserLoginErrorDetail>).detail)
578
- })
579
- login.configure({ transport, applicationName: 'HrSaas', initialMethod: 'qr-code' })
580
- document.body.append(login)
581
- await settle(login)
582
-
583
- await vi.advanceTimersByTimeAsync(500)
584
- await settle(login)
585
-
586
- expect(login.shadowRoot?.textContent).toContain('登录已取消')
587
- expect(login.shadowRoot?.textContent).toContain('已在手机上取消登录,请点击刷新。')
588
- expect(vi.getTimerCount()).toBe(0)
589
- expect(errors).toEqual([
590
- {
591
- code: 'QR_LOGIN_REJECTED',
592
- message: '已在手机上取消登录,请点击刷新。',
593
- recoverable: true,
594
- },
595
- ])
596
- })
537
+ it.skipIf(!USER_LOGIN_QR_LOGIN_ENABLED)(
538
+ 'keeps QR status in one panel and clears polling after confirmation',
539
+ async () => {
540
+ vi.useFakeTimers()
541
+ const createQrChallenge = vi.fn(async () => ({
542
+ challengeId: 'obviously-fake-qr-challenge',
543
+ imageUrl:
544
+ 'data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" width="120" height="120"%3E%3Crect width="120" height="120"/%3E%3C/svg%3E',
545
+ pollIntervalMs: 750,
546
+ }))
547
+ const pollQrChallenge = vi
548
+ .fn()
549
+ .mockResolvedValueOnce({ status: 'scanned' })
550
+ .mockResolvedValueOnce({
551
+ status: 'confirmed',
552
+ result: { applicationName: 'HrSaas' },
553
+ })
554
+ const transport = createTransport({ createQrChallenge, pollQrChallenge })
555
+ const login = document.createElement(USER_LOGIN_TAG)
556
+ const loginEvents: unknown[] = []
557
+ login.addEventListener('login-success', (event) => {
558
+ loginEvents.push((event as CustomEvent).detail)
559
+ })
560
+ login.configure({
561
+ transport,
562
+ applicationName: 'HrSaas',
563
+ initialMethod: 'qr-code',
564
+ qrPollIntervalMs: 500,
565
+ })
566
+ document.body.append(login)
567
+ await settle(login)
568
+
569
+ expect(createQrChallenge).toHaveBeenCalledWith({ applicationName: 'HrSaas' })
570
+ expect(login.shadowRoot?.querySelector<HTMLImageElement>('.qr-image')?.src).toContain(
571
+ 'data:image/svg+xml',
572
+ )
573
+ expect(login.shadowRoot?.textContent).toContain('请使用微信扫描小程序码登录')
574
+ expect(login.shadowRoot?.textContent).toContain('未登录时先完成登录')
575
+ expect(login.shadowRoot?.textContent).toContain('电脑端同步登录')
576
+ expect(vi.getTimerCount()).toBe(1)
577
+ expect(login.shadowRoot?.innerHTML).not.toContain('obviously-fake-qr-challenge')
578
+
579
+ await vi.advanceTimersByTimeAsync(500)
580
+ expect(pollQrChallenge).not.toHaveBeenCalled()
581
+ await vi.advanceTimersByTimeAsync(250)
582
+ await settle(login)
583
+ expect(login.shadowRoot?.textContent).toContain('扫码成功,请在手机上确认')
584
+ expect(vi.getTimerCount()).toBe(1)
585
+ expect(pollQrChallenge).toHaveBeenCalledWith('obviously-fake-qr-challenge', 'HrSaas')
586
+
587
+ await vi.advanceTimersByTimeAsync(750)
588
+ await settle(login)
589
+ expect(loginEvents).toEqual([{ applicationName: 'HrSaas' }])
590
+ expect(JSON.stringify(loginEvents)).not.toContain('obviously-fake-qr-challenge')
591
+ expect(vi.getTimerCount()).toBe(0)
592
+ },
593
+ )
594
+
595
+ it.skipIf(!USER_LOGIN_QR_LOGIN_ENABLED)(
596
+ 'stops polling and offers a refresh after QR login is rejected',
597
+ async () => {
598
+ vi.useFakeTimers()
599
+ const transport = createTransport({
600
+ createQrChallenge: vi.fn(async () => ({
601
+ challengeId: 'obviously-fake-rejected-challenge',
602
+ imageUrl: 'data:image/png;base64,b2J2aW91c2x5LWZha2UtcG5n',
603
+ pollIntervalMs: 500,
604
+ })),
605
+ pollQrChallenge: vi.fn(async () => ({ status: 'rejected' as const })),
606
+ })
607
+ const errors: UserLoginErrorDetail[] = []
608
+ const login = document.createElement(USER_LOGIN_TAG)
609
+ login.addEventListener('error', (event) => {
610
+ errors.push((event as unknown as CustomEvent<UserLoginErrorDetail>).detail)
611
+ })
612
+ login.configure({ transport, applicationName: 'HrSaas', initialMethod: 'qr-code' })
613
+ document.body.append(login)
614
+ await settle(login)
615
+
616
+ await vi.advanceTimersByTimeAsync(500)
617
+ await settle(login)
618
+
619
+ expect(login.shadowRoot?.textContent).toContain('登录已取消')
620
+ expect(login.shadowRoot?.textContent).toContain('已在手机上取消登录,请点击刷新。')
621
+ expect(vi.getTimerCount()).toBe(0)
622
+ expect(errors).toEqual([
623
+ {
624
+ code: 'QR_LOGIN_REJECTED',
625
+ message: '已在手机上取消登录,请点击刷新。',
626
+ recoverable: true,
627
+ },
628
+ ])
629
+ },
630
+ )
597
631
 
598
632
  it('maps a locked password account to a stable safe component error', async () => {
599
633
  const transport = createTransport({
package/restart ADDED
@@ -0,0 +1,4 @@
1
+ #! /bin/bash
2
+ git pull
3
+ pnpm i
4
+ pnpm build