Skip to content

Commit

Permalink
Merge pull request #377 from Telegram-Mini-Apps/376-bug-unhandled-exc…
Browse files Browse the repository at this point in the history
…eption-while-closing-the-qr-scanner-screen

Fix invalid QR scanner behavior related to the invalid isOpened property updates.
  • Loading branch information
heyqbnk authored Jul 2, 2024
2 parents 4d5fbe5 + 9173cad commit e3cc01c
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 11 deletions.
5 changes: 5 additions & 0 deletions .changeset/tasty-turtles-dress.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@tma.js/sdk": patch
---

Fix invalid QR scanner behavior related to the invalid isOpened property updates.
74 changes: 74 additions & 0 deletions packages/sdk/src/components/QRScanner/QRScanner.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';

import { createWindow } from '@test-utils/createWindow.js';
import { dispatchWindowMessageEvent } from '@test-utils/dispatchWindowMessageEvent.js';
import { resetMiniAppsEventEmitter } from '@/bridge/events/event-emitter/singleton.js';

import { QRScanner } from './QRScanner.js';

beforeEach(() => {
createWindow();
});

afterEach(() => {
vi.restoreAllMocks();
resetMiniAppsEventEmitter();
});

describe('open', () => {
it('should call the web_app_open_scan_qr_popup method with the specified text', async () => {
const postEvent = vi.fn();
const qr = new QRScanner(false, '10', postEvent);
qr.open('Scan a QR');

expect(postEvent).toBeCalledTimes(1);
expect(postEvent).toBeCalledWith('web_app_open_scan_qr_popup', {
text: 'Scan a QR',
});
});

it('should resolve with null if the scan_qr_popup_closed event was received', async () => {
const qr = new QRScanner(false, '10', vi.fn() as any);
const promise = qr.open('Scan a QR');

dispatchWindowMessageEvent('scan_qr_popup_closed', {});

await expect(promise).resolves.toBeNull();
});

it('should resolve with qr context specified in the qr_text_received event', async () => {
const qr = new QRScanner(false, '10', vi.fn() as any);
const promise = qr.open('Scan a QR');

dispatchWindowMessageEvent('qr_text_received', {
data: 'qr content',
});

await expect(promise).resolves.toBe('qr content');
});

it('should throw an error, if the scanner was opened several times without closing', async () => {
const qr = new QRScanner(false, '10', vi.fn() as any);
qr.open('Scan a QR');
await expect(qr.open('again')).rejects.toThrow('The scanner is already opened');
});

it('should set isOpened = true after call and false after qr_text_received or scan_qr_popup_closed events were called', async () => {
const qr = new QRScanner(false, '10', vi.fn() as any);

expect(qr.isOpened).toBe(false);
let promise = qr.open();
expect(qr.isOpened).toBe(true);
dispatchWindowMessageEvent('qr_text_received', {
data: 'qr content',
});
await promise;
expect(qr.isOpened).toBe(false);

promise = qr.open();
expect(qr.isOpened).toBe(true);
dispatchWindowMessageEvent('scan_qr_popup_closed', {});
await promise;
expect(qr.isOpened).toBe(false);
});
});
21 changes: 10 additions & 11 deletions packages/sdk/src/components/QRScanner/QRScanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export class QRScanner extends WithSupportsAndTrackableState<QRScannerState, 'cl
}

/**
* Closes scanner.
* Closes the scanner.
*/
close(): void {
this.postEvent('web_app_close_scan_qr_popup');
Expand All @@ -31,29 +31,27 @@ export class QRScanner extends WithSupportsAndTrackableState<QRScannerState, 'cl
}

/**
* Returns true in case, QR scanner is currently opened.
* Returns true if the scanner is currently opened.
*/
get isOpened(): boolean {
return this.get('isOpened');
}

/**
* Opens scanner with specified title shown to user. Method returns promise
* with scanned QR content in case, it was scanned. It will contain null in
* case, scanner was closed.
* Opens the scanner with the specified title shown to user.
* The method returns a promise with a scanned QR content and null if the scanner was closed.
* @param options - method options.
*/
async open(options?: QRScannerOpenOptions): Promise<string | null>;
/**
* Opens scanner with specified title shown to user. Method returns promise
* with scanned QR content in case, it was scanned. It will contain null in
* case, scanner was closed.
* Opens the scanner with the specified title shown to user.
* The method returns a promise with a scanned QR content and null if the scanner was closed.
* @param text - title to display.
*/
async open(text?: string): Promise<string | null>;
async open(textOrOptions?: QRScannerOpenOptions | string): Promise<string | null> {
if (this.isOpened) {
throw new Error('QR scanner is already opened.');
throw new Error('The scanner is already opened');
}

const { text, capture }: QRScannerOpenOptions = (
Expand All @@ -79,9 +77,10 @@ export class QRScanner extends WithSupportsAndTrackableState<QRScannerState, 'cl
this.close();
}
return qr;
} catch(e) {
} finally {
this.isOpened = false;
throw e;
}
}

// TODO: Streaming mode, allowing to scan several QRs until closed.
}

0 comments on commit e3cc01c

Please sign in to comment.