forked from baron/baron-sso
code check 오류 수정
This commit is contained in:
14
Makefile
14
Makefile
@@ -117,6 +117,10 @@ endif
|
|||||||
|
|
||||||
.PHONY: code-check code-check-lint code-check-test-jobs code-check-i18n code-check-go-lint code-check-sync-userfront-locales code-check-userfront-install code-check-userfront-lint code-check-front-lint code-check-backend-tests code-check-userfront-tests code-check-adminfront-tests code-check-devfront-tests code-check-userfront-e2e-tests
|
.PHONY: code-check code-check-lint code-check-test-jobs code-check-i18n code-check-go-lint code-check-sync-userfront-locales code-check-userfront-install code-check-userfront-lint code-check-front-lint code-check-backend-tests code-check-userfront-tests code-check-adminfront-tests code-check-devfront-tests code-check-userfront-e2e-tests
|
||||||
|
|
||||||
|
CODE_CHECK_TEST_JOBS ?= 1
|
||||||
|
PLAYWRIGHT_WORKERS ?= 1
|
||||||
|
FLUTTER_TEST_CONCURRENCY ?= 1
|
||||||
|
|
||||||
code-check: code-check-lint code-check-test-jobs
|
code-check: code-check-lint code-check-test-jobs
|
||||||
@echo "code-check complete."
|
@echo "code-check complete."
|
||||||
|
|
||||||
@@ -124,7 +128,7 @@ code-check-lint: code-check-i18n code-check-front-lint code-check-go-lint code-c
|
|||||||
|
|
||||||
code-check-test-jobs:
|
code-check-test-jobs:
|
||||||
@echo "==> run CI-equivalent test jobs (parallel)"
|
@echo "==> run CI-equivalent test jobs (parallel)"
|
||||||
@$(MAKE) --no-print-directory -j5 --output-sync=target \
|
@$(MAKE) --no-print-directory -j$(CODE_CHECK_TEST_JOBS) --output-sync=target \
|
||||||
code-check-backend-tests \
|
code-check-backend-tests \
|
||||||
code-check-userfront-tests \
|
code-check-userfront-tests \
|
||||||
code-check-userfront-e2e-tests \
|
code-check-userfront-e2e-tests \
|
||||||
@@ -203,11 +207,11 @@ code-check-userfront-tests:
|
|||||||
rm -rf "$$tmp_dir/userfront/.dart_tool" "$$tmp_dir/userfront/build"; \
|
rm -rf "$$tmp_dir/userfront/.dart_tool" "$$tmp_dir/userfront/build"; \
|
||||||
fi; \
|
fi; \
|
||||||
cd "$$tmp_dir" && /bin/sh ./scripts/sync_userfront_locales.sh; \
|
cd "$$tmp_dir" && /bin/sh ./scripts/sync_userfront_locales.sh; \
|
||||||
cd "$$tmp_dir/userfront" && flutter test
|
cd "$$tmp_dir/userfront" && flutter test --concurrency=$(FLUTTER_TEST_CONCURRENCY)
|
||||||
|
|
||||||
code-check-adminfront-tests:
|
code-check-adminfront-tests:
|
||||||
@echo "==> adminfront tests"
|
@echo "==> adminfront tests"
|
||||||
./scripts/run_adminfront_ci_tests.sh adminfront-tests
|
PLAYWRIGHT_WORKERS=$(PLAYWRIGHT_WORKERS) ./scripts/run_adminfront_ci_tests.sh adminfront-tests
|
||||||
|
|
||||||
code-check-devfront-tests:
|
code-check-devfront-tests:
|
||||||
@echo "==> devfront tests"
|
@echo "==> devfront tests"
|
||||||
@@ -219,7 +223,7 @@ code-check-devfront-tests:
|
|||||||
(cd devfront && $(PLAYWRIGHT_INSTALL_ALL)) || status=$$?; \
|
(cd devfront && $(PLAYWRIGHT_INSTALL_ALL)) || status=$$?; \
|
||||||
fi; \
|
fi; \
|
||||||
if [ $$status -eq 0 ]; then \
|
if [ $$status -eq 0 ]; then \
|
||||||
(cd devfront && npm test) || status=$$?; \
|
(cd devfront && PLAYWRIGHT_WORKERS=$(PLAYWRIGHT_WORKERS) npm test) || status=$$?; \
|
||||||
fi; \
|
fi; \
|
||||||
[ -d devfront/playwright-report ] && cp -R devfront/playwright-report reports/devfront/ || true; \
|
[ -d devfront/playwright-report ] && cp -R devfront/playwright-report reports/devfront/ || true; \
|
||||||
[ -d devfront/test-results ] && cp -R devfront/test-results reports/devfront/ || true; \
|
[ -d devfront/test-results ] && cp -R devfront/test-results reports/devfront/ || true; \
|
||||||
@@ -267,7 +271,7 @@ code-check-userfront-e2e-tests:
|
|||||||
if [ $$status -eq 0 ]; then \
|
if [ $$status -eq 0 ]; then \
|
||||||
port="$$(node -e "const net=require('node:net'); const s=net.createServer(); s.listen(0,'127.0.0.1',()=>{console.log(s.address().port); s.close();});")"; \
|
port="$$(node -e "const net=require('node:net'); const s=net.createServer(); s.listen(0,'127.0.0.1',()=>{console.log(s.address().port); s.close();});")"; \
|
||||||
echo "==> userfront-e2e using PORT=$$port"; \
|
echo "==> userfront-e2e using PORT=$$port"; \
|
||||||
(cd "$$tmp_dir/userfront-e2e" && PORT=$$port npm test) || status=$$?; \
|
(cd "$$tmp_dir/userfront-e2e" && PORT=$$port PLAYWRIGHT_WORKERS=$(PLAYWRIGHT_WORKERS) npm test) || status=$$?; \
|
||||||
fi; \
|
fi; \
|
||||||
[ -d "$$tmp_dir/userfront-e2e/playwright-report" ] && cp -R "$$tmp_dir/userfront-e2e/playwright-report" reports/userfront-e2e/ || true; \
|
[ -d "$$tmp_dir/userfront-e2e/playwright-report" ] && cp -R "$$tmp_dir/userfront-e2e/playwright-report" reports/userfront-e2e/ || true; \
|
||||||
[ -d "$$tmp_dir/userfront-e2e/test-results" ] && cp -R "$$tmp_dir/userfront-e2e/test-results" reports/userfront-e2e/ || true; \
|
[ -d "$$tmp_dir/userfront-e2e/test-results" ] && cp -R "$$tmp_dir/userfront-e2e/test-results" reports/userfront-e2e/ || true; \
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
import { defineConfig, devices } from "@playwright/test";
|
import { defineConfig, devices } from "@playwright/test";
|
||||||
|
|
||||||
|
const configuredWorkers = process.env.PLAYWRIGHT_WORKERS
|
||||||
|
? Number.parseInt(process.env.PLAYWRIGHT_WORKERS, 10)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read environment variables from file.
|
* Read environment variables from file.
|
||||||
* https://github.com/motdotla/dotenv
|
* https://github.com/motdotla/dotenv
|
||||||
@@ -24,7 +28,7 @@ export default defineConfig({
|
|||||||
/* Retry on CI only */
|
/* Retry on CI only */
|
||||||
retries: process.env.CI ? 2 : 0,
|
retries: process.env.CI ? 2 : 0,
|
||||||
/* Opt out of parallel tests on CI. */
|
/* Opt out of parallel tests on CI. */
|
||||||
workers: process.env.CI ? 1 : undefined,
|
workers: configuredWorkers ?? (process.env.CI ? 1 : undefined),
|
||||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||||
reporter: "html",
|
reporter: "html",
|
||||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
import { defineConfig, devices } from "@playwright/test";
|
import { defineConfig, devices } from "@playwright/test";
|
||||||
|
|
||||||
|
const configuredWorkers = process.env.PLAYWRIGHT_WORKERS
|
||||||
|
? Number.parseInt(process.env.PLAYWRIGHT_WORKERS, 10)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read environment variables from file.
|
* Read environment variables from file.
|
||||||
* https://github.com/motdotla/dotenv
|
* https://github.com/motdotla/dotenv
|
||||||
@@ -20,7 +24,7 @@ export default defineConfig({
|
|||||||
/* Retry on CI only */
|
/* Retry on CI only */
|
||||||
retries: process.env.CI ? 2 : 0,
|
retries: process.env.CI ? 2 : 0,
|
||||||
/* Opt out of parallel tests on CI. */
|
/* Opt out of parallel tests on CI. */
|
||||||
workers: process.env.CI ? 1 : undefined,
|
workers: configuredWorkers ?? (process.env.CI ? 1 : undefined),
|
||||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||||
reporter: "html",
|
reporter: "html",
|
||||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||||
|
|||||||
@@ -4,13 +4,16 @@ const port = Number.parseInt(process.env.PORT ?? '4173', 10);
|
|||||||
const defaultBaseUrl = `http://127.0.0.1:${port}`;
|
const defaultBaseUrl = `http://127.0.0.1:${port}`;
|
||||||
const baseURL = process.env.BASE_URL ?? defaultBaseUrl;
|
const baseURL = process.env.BASE_URL ?? defaultBaseUrl;
|
||||||
const reuseExistingServer = !process.env.CI;
|
const reuseExistingServer = !process.env.CI;
|
||||||
|
const configuredWorkers = process.env.PLAYWRIGHT_WORKERS
|
||||||
|
? Number.parseInt(process.env.PLAYWRIGHT_WORKERS, 10)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
testDir: './tests',
|
testDir: './tests',
|
||||||
fullyParallel: false,
|
fullyParallel: false,
|
||||||
forbidOnly: !!process.env.CI,
|
forbidOnly: !!process.env.CI,
|
||||||
retries: process.env.CI ? 2 : 0,
|
retries: process.env.CI ? 2 : 0,
|
||||||
workers: process.env.CI ? 1 : undefined,
|
workers: configuredWorkers ?? (process.env.CI ? 1 : undefined),
|
||||||
reporter: process.env.CI ? [['html', { open: 'never' }], ['list']] : 'html',
|
reporter: process.env.CI ? [['html', { open: 'never' }], ['list']] : 'html',
|
||||||
use: {
|
use: {
|
||||||
baseURL,
|
baseURL,
|
||||||
|
|||||||
@@ -47,6 +47,11 @@ async function blurDepartmentEditor(page: Page): Promise<void> {
|
|||||||
await page.waitForTimeout(250);
|
await page.waitForTimeout(250);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function submitDepartmentEditor(page: Page): Promise<void> {
|
||||||
|
await page.keyboard.press('Enter');
|
||||||
|
await page.waitForTimeout(250);
|
||||||
|
}
|
||||||
|
|
||||||
async function mockProfileApis(page: Page, state: ProfileState): Promise<void> {
|
async function mockProfileApis(page: Page, state: ProfileState): Promise<void> {
|
||||||
await page.route('**/api/v1/**', async (route: Route) => {
|
await page.route('**/api/v1/**', async (route: Route) => {
|
||||||
const request = route.request();
|
const request = route.request();
|
||||||
@@ -155,7 +160,7 @@ test.describe('UserFront WASM profile department editing', () => {
|
|||||||
await page.unroute('**/api/v1/**');
|
await page.unroute('**/api/v1/**');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('소속 수정 후 포커스 아웃하면 저장 요청이 전송되고 새로고침 후 최신 값으로 재조회된다', async ({
|
test('소속 수정 후 명시 저장하면 저장 요청이 전송되고 새로고침 후 최신 값으로 재조회된다', async ({
|
||||||
page,
|
page,
|
||||||
}) => {
|
}) => {
|
||||||
const state: ProfileState = {
|
const state: ProfileState = {
|
||||||
@@ -170,7 +175,7 @@ test.describe('UserFront WASM profile department editing', () => {
|
|||||||
|
|
||||||
await openDepartmentEditor(page);
|
await openDepartmentEditor(page);
|
||||||
await fillAt(page, PROFILE_DEPARTMENT_INPUT_X, PROFILE_DEPARTMENT_INPUT_Y, 'QA-Updated');
|
await fillAt(page, PROFILE_DEPARTMENT_INPUT_X, PROFILE_DEPARTMENT_INPUT_Y, 'QA-Updated');
|
||||||
await blurDepartmentEditor(page);
|
await submitDepartmentEditor(page);
|
||||||
|
|
||||||
await expect.poll(() => state.putBodies.length).toBe(1);
|
await expect.poll(() => state.putBodies.length).toBe(1);
|
||||||
expect(state.putBodies[0]?.department).toBe('QA-Updated');
|
expect(state.putBodies[0]?.department).toBe('QA-Updated');
|
||||||
@@ -248,7 +253,7 @@ test.describe('UserFront WASM profile department editing', () => {
|
|||||||
expect(state.department).toBe('QA');
|
expect(state.department).toBe('QA');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('소속을 수정한 뒤 새로고침 후 다시 수정해도 저장 요청이 누락되지 않는다', async ({ page }) => {
|
test('소속을 저장한 뒤 새로고침 후 다시 저장해도 저장 요청이 누락되지 않는다', async ({ page }) => {
|
||||||
const state: ProfileState = {
|
const state: ProfileState = {
|
||||||
department: 'QA',
|
department: 'QA',
|
||||||
getMeCount: 0,
|
getMeCount: 0,
|
||||||
@@ -261,7 +266,7 @@ test.describe('UserFront WASM profile department editing', () => {
|
|||||||
|
|
||||||
await openDepartmentEditor(page);
|
await openDepartmentEditor(page);
|
||||||
await fillAt(page, PROFILE_DEPARTMENT_INPUT_X, PROFILE_DEPARTMENT_INPUT_Y, 'QA-1');
|
await fillAt(page, PROFILE_DEPARTMENT_INPUT_X, PROFILE_DEPARTMENT_INPUT_Y, 'QA-1');
|
||||||
await blurDepartmentEditor(page);
|
await submitDepartmentEditor(page);
|
||||||
await expect.poll(() => state.putBodies.length).toBe(1);
|
await expect.poll(() => state.putBodies.length).toBe(1);
|
||||||
|
|
||||||
await page.reload();
|
await page.reload();
|
||||||
@@ -270,7 +275,7 @@ test.describe('UserFront WASM profile department editing', () => {
|
|||||||
|
|
||||||
await openDepartmentEditor(page);
|
await openDepartmentEditor(page);
|
||||||
await fillAt(page, PROFILE_DEPARTMENT_INPUT_X, PROFILE_DEPARTMENT_INPUT_Y, 'QA-2');
|
await fillAt(page, PROFILE_DEPARTMENT_INPUT_X, PROFILE_DEPARTMENT_INPUT_Y, 'QA-2');
|
||||||
await blurDepartmentEditor(page);
|
await submitDepartmentEditor(page);
|
||||||
await expect.poll(() => state.putBodies.length).toBe(2);
|
await expect.poll(() => state.putBodies.length).toBe(2);
|
||||||
|
|
||||||
expect(state.putBodies[0]?.department).toBe('QA-1');
|
expect(state.putBodies[0]?.department).toBe('QA-1');
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -277,6 +277,11 @@ privacy_full = "개인정보 수집 및 이용 동의 전문..."
|
|||||||
tos_full = "서비스 이용약관 전문..."
|
tos_full = "서비스 이용약관 전문..."
|
||||||
|
|
||||||
[msg.userfront.signup.agreement]
|
[msg.userfront.signup.agreement]
|
||||||
|
all_hint = "필수 약관 2개를 모두 확인하고 동의하면 다음 단계로 진행할 수 있습니다."
|
||||||
|
description = "계속 진행하려면 서비스 이용 조건과 개인정보 수집·이용 항목을 확인한 뒤 동의해주세요."
|
||||||
|
privacy_summary = "개인정보 수집 항목, 이용 목적, 보관 기준을 안내합니다."
|
||||||
|
progress = "필수 약관 {total}개 중 {count}개 동의 완료"
|
||||||
|
tos_summary = "서비스 이용 조건과 책임 범위를 확인할 수 있습니다."
|
||||||
title = "서비스 이용을 위해\n약관에 동의해주세요"
|
title = "서비스 이용을 위해\n약관에 동의해주세요"
|
||||||
|
|
||||||
[msg.userfront.signup.auth]
|
[msg.userfront.signup.auth]
|
||||||
@@ -583,6 +588,7 @@ title = "회원가입"
|
|||||||
[ui.userfront.signup.agreement]
|
[ui.userfront.signup.agreement]
|
||||||
all = "모두 동의합니다"
|
all = "모두 동의합니다"
|
||||||
privacy_title = "개인정보 수집 및 이용 동의 (필수)"
|
privacy_title = "개인정보 수집 및 이용 동의 (필수)"
|
||||||
|
required = "필수"
|
||||||
tos_title = "바론 소프트웨어 이용약관 (필수)"
|
tos_title = "바론 소프트웨어 이용약관 (필수)"
|
||||||
|
|
||||||
[ui.userfront.signup.auth]
|
[ui.userfront.signup.auth]
|
||||||
@@ -616,4 +622,3 @@ verify = "본인인증"
|
|||||||
|
|
||||||
[ui.userfront.signup.success]
|
[ui.userfront.signup.success]
|
||||||
action = "로그인하기"
|
action = "로그인하기"
|
||||||
|
|
||||||
|
|||||||
@@ -277,6 +277,11 @@ privacy_full = ""
|
|||||||
tos_full = ""
|
tos_full = ""
|
||||||
|
|
||||||
[msg.userfront.signup.agreement]
|
[msg.userfront.signup.agreement]
|
||||||
|
all_hint = ""
|
||||||
|
description = ""
|
||||||
|
privacy_summary = ""
|
||||||
|
progress = ""
|
||||||
|
tos_summary = ""
|
||||||
title = ""
|
title = ""
|
||||||
|
|
||||||
[msg.userfront.signup.auth]
|
[msg.userfront.signup.auth]
|
||||||
@@ -583,6 +588,7 @@ title = ""
|
|||||||
[ui.userfront.signup.agreement]
|
[ui.userfront.signup.agreement]
|
||||||
all = ""
|
all = ""
|
||||||
privacy_title = ""
|
privacy_title = ""
|
||||||
|
required = ""
|
||||||
tos_title = ""
|
tos_title = ""
|
||||||
|
|
||||||
[ui.userfront.signup.auth]
|
[ui.userfront.signup.auth]
|
||||||
@@ -616,4 +622,3 @@ verify = ""
|
|||||||
|
|
||||||
[ui.userfront.signup.success]
|
[ui.userfront.signup.success]
|
||||||
action = ""
|
action = ""
|
||||||
|
|
||||||
|
|||||||
@@ -238,8 +238,8 @@ class _ConsentScreenState extends State<ConsentScreen> {
|
|||||||
final clientName = (clientRawName != null && clientRawName.isNotEmpty)
|
final clientName = (clientRawName != null && clientRawName.isNotEmpty)
|
||||||
? clientRawName
|
? clientRawName
|
||||||
: (clientId != '-'
|
: (clientId != '-'
|
||||||
? clientId
|
? clientId
|
||||||
: tr('msg.userfront.consent.client_unknown'));
|
: tr('msg.userfront.consent.client_unknown'));
|
||||||
final clientLogo = _consentInfo?['client']?['logo_uri'];
|
final clientLogo = _consentInfo?['client']?['logo_uri'];
|
||||||
final requestedScopes =
|
final requestedScopes =
|
||||||
(_consentInfo?['requested_scope'] as List<dynamic>?)?.cast<String>() ??
|
(_consentInfo?['requested_scope'] as List<dynamic>?)?.cast<String>() ??
|
||||||
|
|||||||
@@ -1494,8 +1494,7 @@ class _SignupScreenState extends State<SignupScreen> {
|
|||||||
labelText: tr(
|
labelText: tr(
|
||||||
'ui.userfront.signup.profile.company',
|
'ui.userfront.signup.profile.company',
|
||||||
),
|
),
|
||||||
border:
|
border: const OutlineInputBorder(),
|
||||||
const OutlineInputBorder(),
|
|
||||||
),
|
),
|
||||||
items: [
|
items: [
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
@@ -1557,7 +1556,9 @@ class _SignupScreenState extends State<SignupScreen> {
|
|||||||
_buildProfileFieldGroup(
|
_buildProfileFieldGroup(
|
||||||
title: _affiliationType == 'AFFILIATE'
|
title: _affiliationType == 'AFFILIATE'
|
||||||
? tr('ui.userfront.signup.profile.department')
|
? tr('ui.userfront.signup.profile.department')
|
||||||
: tr('ui.userfront.signup.profile.department_optional'),
|
: tr(
|
||||||
|
'ui.userfront.signup.profile.department_optional',
|
||||||
|
),
|
||||||
description: _affiliationType == 'AFFILIATE'
|
description: _affiliationType == 'AFFILIATE'
|
||||||
? '가족사 사용자는 부서명을 입력해주세요.'
|
? '가족사 사용자는 부서명을 입력해주세요.'
|
||||||
: '선택 입력 항목입니다.',
|
: '선택 입력 항목입니다.',
|
||||||
@@ -1677,10 +1678,7 @@ class _SignupScreenState extends State<SignupScreen> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (trailing != null) ...[
|
if (trailing != null) ...[const SizedBox(width: 12), trailing],
|
||||||
const SizedBox(width: 12),
|
|
||||||
trailing,
|
|
||||||
],
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
SizedBox(height: isDesktop ? 18 : 14),
|
SizedBox(height: isDesktop ? 18 : 14),
|
||||||
@@ -1793,13 +1791,22 @@ class _SignupScreenState extends State<SignupScreen> {
|
|||||||
hasTypeCount,
|
hasTypeCount,
|
||||||
),
|
),
|
||||||
if (requiresUpper)
|
if (requiresUpper)
|
||||||
_cryptoCheck(tr('msg.userfront.signup.password.rule.uppercase'), hasUpper),
|
_cryptoCheck(
|
||||||
|
tr('msg.userfront.signup.password.rule.uppercase'),
|
||||||
|
hasUpper,
|
||||||
|
),
|
||||||
if (requiresLower)
|
if (requiresLower)
|
||||||
_cryptoCheck(tr('msg.userfront.signup.password.rule.lowercase'), hasLower),
|
_cryptoCheck(
|
||||||
|
tr('msg.userfront.signup.password.rule.lowercase'),
|
||||||
|
hasLower,
|
||||||
|
),
|
||||||
if (requiresNumber)
|
if (requiresNumber)
|
||||||
_cryptoCheck(tr('msg.userfront.signup.password.rule.number'), hasDigit),
|
_cryptoCheck(tr('msg.userfront.signup.password.rule.number'), hasDigit),
|
||||||
if (requiresSymbol)
|
if (requiresSymbol)
|
||||||
_cryptoCheck(tr('msg.userfront.signup.password.rule.symbol'), hasSpecial),
|
_cryptoCheck(
|
||||||
|
tr('msg.userfront.signup.password.rule.symbol'),
|
||||||
|
hasSpecial,
|
||||||
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
return LayoutBuilder(
|
return LayoutBuilder(
|
||||||
@@ -1861,14 +1868,15 @@ class _SignupScreenState extends State<SignupScreen> {
|
|||||||
obscureText: _isPasswordObscured,
|
obscureText: _isPasswordObscured,
|
||||||
onChanged: (_) => setState(() {}),
|
onChanged: (_) => setState(() {}),
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: tr('ui.userfront.signup.password.label'),
|
labelText: tr(
|
||||||
|
'ui.userfront.signup.password.label',
|
||||||
|
),
|
||||||
border: const OutlineInputBorder(),
|
border: const OutlineInputBorder(),
|
||||||
errorText: _passwordError,
|
errorText: _passwordError,
|
||||||
suffixIcon: IconButton(
|
suffixIcon: IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
setState(() {
|
setState(() {
|
||||||
_isPasswordObscured =
|
_isPasswordObscured = !_isPasswordObscured;
|
||||||
!_isPasswordObscured;
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
@@ -1897,8 +1905,8 @@ class _SignupScreenState extends State<SignupScreen> {
|
|||||||
obscureText: _isConfirmPasswordObscured,
|
obscureText: _isConfirmPasswordObscured,
|
||||||
onChanged: (val) {
|
onChanged: (val) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_confirmPasswordError = (val !=
|
_confirmPasswordError =
|
||||||
_passwordController.text)
|
(val != _passwordController.text)
|
||||||
? tr('msg.userfront.signup.password.mismatch')
|
? tr('msg.userfront.signup.password.mismatch')
|
||||||
: null;
|
: null;
|
||||||
});
|
});
|
||||||
@@ -2032,11 +2040,7 @@ class _SignupScreenState extends State<SignupScreen> {
|
|||||||
),
|
),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.all(isDesktop ? 16 : 14),
|
padding: EdgeInsets.all(isDesktop ? 16 : 14),
|
||||||
child: Wrap(
|
child: Wrap(spacing: 12, runSpacing: 10, children: checks),
|
||||||
spacing: 12,
|
|
||||||
runSpacing: 10,
|
|
||||||
children: checks,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ class MockProfileNotifier extends ProfileNotifier {
|
|||||||
UserProfile? _profile;
|
UserProfile? _profile;
|
||||||
bool updateCalled = false;
|
bool updateCalled = false;
|
||||||
String? updatedName;
|
String? updatedName;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<UserProfile?> build() async {
|
Future<UserProfile?> build() async {
|
||||||
_profile = UserProfile(
|
_profile = UserProfile(
|
||||||
@@ -33,7 +33,11 @@ class MockProfileNotifier extends ProfileNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> updateProfile({String? name, String? phone, String? department}) async {
|
Future<void> updateProfile({
|
||||||
|
String? name,
|
||||||
|
String? phone,
|
||||||
|
String? department,
|
||||||
|
}) async {
|
||||||
updateCalled = true;
|
updateCalled = true;
|
||||||
updatedName = name;
|
updatedName = name;
|
||||||
_profile = _profile!.copyWith(
|
_profile = _profile!.copyWith(
|
||||||
@@ -46,75 +50,82 @@ class MockProfileNotifier extends ProfileNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
testWidgets('ProfilePage explicit save button UX flow (Edit -> Cancel -> Edit -> Save)', (tester) async {
|
testWidgets(
|
||||||
final recordedErrors = <FlutterErrorDetails>[];
|
'ProfilePage explicit save button UX flow (Edit -> Cancel -> Edit -> Save)',
|
||||||
final previousOnError = FlutterError.onError;
|
(tester) async {
|
||||||
FlutterError.onError = (details) {
|
final recordedErrors = <FlutterErrorDetails>[];
|
||||||
final text = details.exceptionAsString();
|
final previousOnError = FlutterError.onError;
|
||||||
if (text.contains('A RenderFlex overflowed')) {
|
FlutterError.onError = (details) {
|
||||||
return;
|
final text = details.exceptionAsString();
|
||||||
}
|
if (text.contains('A RenderFlex overflowed')) {
|
||||||
recordedErrors.add(details);
|
return;
|
||||||
};
|
}
|
||||||
addTearDown(() {
|
recordedErrors.add(details);
|
||||||
FlutterError.onError = previousOnError;
|
};
|
||||||
});
|
addTearDown(() {
|
||||||
|
FlutterError.onError = previousOnError;
|
||||||
|
});
|
||||||
|
|
||||||
tester.view.physicalSize = const Size(1920, 1080);
|
tester.view.physicalSize = const Size(1920, 1080);
|
||||||
tester.view.devicePixelRatio = 1.0;
|
tester.view.devicePixelRatio = 1.0;
|
||||||
addTearDown(tester.view.resetPhysicalSize);
|
addTearDown(tester.view.resetPhysicalSize);
|
||||||
addTearDown(tester.view.resetDevicePixelRatio);
|
addTearDown(tester.view.resetDevicePixelRatio);
|
||||||
|
|
||||||
final mockNotifier = MockProfileNotifier();
|
final mockNotifier = MockProfileNotifier();
|
||||||
|
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
ProviderScope(
|
ProviderScope(
|
||||||
overrides: [
|
overrides: [profileProvider.overrideWith(() => mockNotifier)],
|
||||||
profileProvider.overrideWith(() => mockNotifier),
|
child: const MaterialApp(home: Scaffold(body: ProfilePage())),
|
||||||
],
|
|
||||||
child: const MaterialApp(
|
|
||||||
home: Scaffold(body: ProfilePage()),
|
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
);
|
|
||||||
|
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
// 1. Entering edit mode
|
// 1. Entering edit mode
|
||||||
final editButton = find.byKey(const Key('profile-name-edit-button'));
|
final editButton = find.byKey(const Key('profile-name-edit-button'));
|
||||||
expect(editButton, findsOneWidget);
|
expect(editButton, findsOneWidget);
|
||||||
await tester.tap(editButton);
|
await tester.tap(editButton);
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
final inputField = find.byKey(const Key('profile-name-input'));
|
final inputField = find.byKey(const Key('profile-name-input'));
|
||||||
expect(inputField, findsOneWidget);
|
expect(inputField, findsOneWidget);
|
||||||
|
|
||||||
// 2. Testing cancel flow
|
// 2. Testing cancel flow
|
||||||
await tester.enterText(inputField, 'Changed Name');
|
await tester.enterText(inputField, 'Changed Name');
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
final cancelButton = find.byKey(const Key('profile-name-cancel-button'));
|
final cancelButton = find.byKey(const Key('profile-name-cancel-button'));
|
||||||
await tester.tap(cancelButton);
|
await tester.tap(cancelButton);
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
// After cancellation, the field should be read-only again.
|
// After cancellation, the field should be read-only again.
|
||||||
expect(find.byKey(const Key('profile-name-input')), findsNothing);
|
expect(find.byKey(const Key('profile-name-input')), findsNothing);
|
||||||
// Find text could be part of ListTile
|
// Find text could be part of ListTile
|
||||||
expect(find.text('Original Name'), findsWidgets);
|
expect(find.text('Original Name'), findsWidgets);
|
||||||
|
|
||||||
// 3. Re-enter edit mode and explicitly save
|
// 3. Re-enter edit mode and explicitly save
|
||||||
await tester.tap(find.byKey(const Key('profile-name-edit-button')));
|
await tester.tap(find.byKey(const Key('profile-name-edit-button')));
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
await tester.enterText(find.byKey(const Key('profile-name-input')), 'Saved Name');
|
await tester.enterText(
|
||||||
await tester.pumpAndSettle();
|
find.byKey(const Key('profile-name-input')),
|
||||||
|
'Saved Name',
|
||||||
final saveButton = find.byKey(const Key('profile-name-save-button'));
|
);
|
||||||
await tester.tap(saveButton);
|
await tester.pumpAndSettle();
|
||||||
await tester.pumpAndSettle();
|
|
||||||
|
final saveButton = find.byKey(const Key('profile-name-save-button'));
|
||||||
// Verify the mock received the update
|
await tester.tap(saveButton);
|
||||||
expect(mockNotifier.updateCalled, isTrue);
|
await tester.pumpAndSettle();
|
||||||
expect(mockNotifier.updatedName, 'Saved Name');
|
await tester.pump(const Duration(seconds: 4));
|
||||||
});
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
FlutterError.onError = previousOnError;
|
||||||
|
|
||||||
|
// Verify the mock received the update
|
||||||
|
expect(mockNotifier.updateCalled, isTrue);
|
||||||
|
expect(mockNotifier.updatedName, 'Saved Name');
|
||||||
|
expect(recordedErrors, isEmpty);
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user