Skip to content

Commit 44b68f8

Browse files
committed
test add contact already in your contact - foundation
1 parent 40c1cac commit 44b68f8

3 files changed

Lines changed: 43 additions & 11 deletions

File tree

docs/pubky-profile-manual-e2e.md

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Pubky profile & contacts — manual E2E charter
22

3-
Charter for QA around profile creation, Pubky Ring import, and contacts. Use while the feature is landing; automate later via WebdriverIO + `ciIt()` when flows and infra are stable.
3+
Charter for QA around profile creation, Pubky Ring import, and contacts. Use alongside the WebdriverIO `@pubky_profile` specs: keep high-value stable flows automated and use the remaining checklist for manual / exploratory coverage while the feature is landing.
44

55
PR context: `bitkit-android#824`, `bitkit-ios#476`.
66

@@ -73,7 +73,7 @@ With no profile created yet, every entry point should funnel into the choice scr
7373
4. Character limits (if enforced by the UI) prevent oversize inputs without crashing.
7474
5. **Delete Profile** lives at the bottom of this Edit screen (the only entry point in the normal UI). Covered by B.8.
7575

76-
### B.4 Add contact (manual)
76+
### B.4 Add contact
7777

7878
1. Contacts → **Add contact** → paste a valid pubky → Continue → contact is added; opening detail shows the remote profile snapshot (name, image, links).
7979
2. Scan-QR path (if exposed): scan a pubky QR → same flow.
@@ -82,21 +82,36 @@ With no profile created yet, every entry point should funnel into the choice scr
8282
5. **Self-add guard**: pasting your own pubky shows an explicit error (Android string: `contacts__add_error_self`); contact is **not** created.
8383
6. **Duplicate add** (same pubky already in contacts) → de-duplicated or clear message.
8484

85+
> **`@pubky_profile_2`** adds one staging contact after editing the profile, then verifies the row persists after app relaunch and wallet restore. **`@pubky_profile_3`** covers malformed pubky and self-add inline validation, then adds both staging contacts. Scan-QR, paste-from-clipboard, duplicate-add handling, and full remote snapshot assertions (avatar/links) remain manual.
86+
8587
### B.5 Edit contact (local snapshot)
8688

8789
1. Contacts → open a contact → Edit.
8890
2. Edits to **Notes** and any local fields **persist locally** and **do not** update the remote Pubky profile of that contact — this is a local snapshot.
8991
3. Name field behavior: verify whether the app allows renaming or is display-only; whichever is shipped should match across Android and iOS.
9092
4. Manual refresh / pull-to-refresh (if present) re-fetches the remote snapshot; local Notes are preserved.
9193

94+
> **`@pubky_profile_4`** creates Wallet A, creates Wallet B, adds Wallet A as a contact on Wallet B, edits Wallet A's contact name/notes on Wallet B, verifies the edited contact detail locally, then restores Wallet A and verifies Wallet A's own profile is unchanged. Manual refresh / pull-to-refresh behavior and local note preservation after refresh are not automated.
95+
9296
### B.6 Contacts list
9397

9498
1. Header: single flat **CONTACTS** section header (no A/B/C alphabetical section headers — this was the Figma-compliant design).
9599
2. "My profile" row is shown at the top when a profile exists.
96100
3. Tapping a contact opens detail; tapping "My profile" opens own profile.
97101
4. Empty state (profile exists, no contacts) shows the empty copy and an Add-contact CTA.
98102

99-
### B.7 Delete profile (homeserver wipe)
103+
> **`@pubky_profile_2`** / **`@pubky_profile_3`** verify contact rows can be found by `Contact_<pubkey>`. **`@pubky_profile_3`** also verifies rows are gone after delete. Header copy, `ContactsMyProfile`, tapping My Profile, and empty-state copy remain manual.
104+
105+
### B.7 Delete contact
106+
107+
1. Contacts → open a contact → Edit → Delete Contact → confirmation → **Yes, Delete**.
108+
2. Success toast appears ("Contact deleted") and the contact row is removed.
109+
3. Deleting the last contact returns to the empty contacts state without a stale header/list state.
110+
4. Error path: if delete fails, show a clear error and keep the contact available.
111+
112+
> **`@pubky_profile_3`** deletes both staging contacts, waits for `ContactDeletedToast`, and verifies each contact row is absent. Delete failure/error handling remains manual.
113+
114+
### B.8 Delete profile (homeserver wipe)
100115

101116
1. Profile → Edit → scroll to the bottom → **Delete Profile** → confirmation ("Delete Profile?" / "This will delete your current Pubky profile data. You can create a new profile for this pubky later.") → confirm.
102117
2. After delete: gated back to `PubkyChoice` (same as **section A**).
@@ -105,12 +120,12 @@ With no profile created yet, every entry point should funnel into the choice scr
105120

106121
> **`@pubky_profile_2`** also ends with delete → recreate and asserts the same seed-derived pubky. Homeserver error path in (4) is manual.
107122
108-
### B.8 Wipe wallet
123+
### B.9 Wipe wallet
109124

110125
1. Settings → reset/wipe wallet → onboard a **new seed** → no profile, different pubky → repeat **section A**.
111126
2. Onboard the **same seed** on a fresh install → profile and contacts should be recovered from the homeserver (if not Deleted first).
112127

113-
> **`@pubky_profile_2`** exercises **(2) in part**: it runs the standard E2E **backup → `restoreWallet(seed)`** flow (after profile exists), then checks profile **details and pubkey** and **pubky (copy)** still match. It is not a full “fresh install from IPA” story.
128+
> **`@pubky_profile_2`** exercises **(2) in part**: it runs the standard E2E **backup → `restoreWallet(seed)`** flow (after profile and one contact exist), then checks profile **details**, **pubky copy**, and the added contact row still match. It is not a full “fresh install from IPA” story.
114129
115130
---
116131

@@ -166,9 +181,9 @@ Use this table to verify persistence expectations. Fill in observed behavior if
166181
| --------------------------------------- | ---- | ----- | -------------- | -------------- | ----------------------- |
167182
| Disconnect from empty state + reconnect | same | same | preserved | re-synced | preserved |
168183
| Delete profile + recreate (same wallet) | same | same | wiped then new | tbd — verify | tbd — verify |
169-
| Wipe wallet, restore same seed | same | same | preserved | re-synced | tbd — verify |
184+
| Wipe wallet, restore same seed | same | same | preserved | re-synced (`@pubky_profile_2`) | tbd — verify |
170185
| Wipe wallet, new seed | new | new | none | none | none |
171-
| App reinstall, same seed | same | same | preserved | re-synced | tbd — verify |
186+
| App reinstall, same seed | same | same | preserved | re-synced (`@pubky_profile_2`) | tbd — verify |
172187

173188
---
174189

@@ -197,9 +212,14 @@ Use this table to verify persistence expectations. Fill in observed behavior if
197212

198213
## H. Automation strategy
199214

200-
- **Spec**: `test/specs/pubky-profile.e2e.ts` — profile-only for now; contacts and Ring stay manual here until more specs exist. How/whether it runs in automation is a pipeline concern (`AGENTS.md`); this section only describes the spec’s intent.
215+
- **Spec**: `test/specs/pubky-profile.e2e.ts`covers profile creation/edit/delete, contact add/delete/edit basics, and persistence. Ring import remains manual until reliable automation exists. How/whether it runs in automation is a pipeline concern (`AGENTS.md`); this section only describes the spec’s intent.
201216
- **Isolation**: `beforeEach` uses `reinstallApp()` and `completeOnboarding()` so a single `ciIt` can be run alone via `--mochaOpts.grep "@pubky_profile_N"`.
202-
- **What the two tests do**: `@pubky_profile_1` — section A gating (no profile). `@pubky_profile_2` — one chained flow: create → copy/verify pubky → update profile, verify on screen → `launchFreshApp`, verify again → **wait for backup, restore wallet from seed**, verify details + pubky → remove link and tag, save, verify → delete profile → create profile again, same pubky. Does not cover Ring, full contacts tests, or avatar.
217+
- **What the tests do**:
218+
- `@pubky_profile_1` — section A gating (no profile).
219+
- `@pubky_profile_2` — create → copy/verify pubky → update profile → add one staging contact → `launchFreshApp`, verify profile + contact → **wait for backup, restore wallet from seed**, verify profile + pubky + contact → remove link and tag → delete profile → create profile again, same pubky.
220+
- `@pubky_profile_3` — invalid pubky and self-add validation → add both staging contacts → delete both contacts and verify rows are absent.
221+
- `@pubky_profile_4` — Wallet A profile edited as a contact on Wallet B remains a local contact edit; restoring Wallet A verifies its own profile was not changed.
222+
- **Known gaps in automation**: Ring import, avatar add/remove, scan-QR contact add, paste-from-clipboard contact add, duplicate-add behavior, contacts list header/My Profile row/empty copy, contact refresh behavior, profile/contact field-boundary behavior, and network/error paths.
203223
- **Tags**: suite `@pubky_profile`; tests `@pubky_profile_1`, `@pubky_profile_2`, …; reserve e.g. `@pubky_ring_required` if you add Ring-specific specs later.
204224
- **Use `ciIt()`** instead of `it()` in this suite so it matches the repo’s `ci_run_*` / lockfile retry pattern when you wire runs up.
205225
- Shared test IDs: **H.1** below. Implementation lives under `test/helpers/` next to other E2E specs.
@@ -216,11 +236,15 @@ Use the **same string** on Android and iOS so specs stay platform-agnostic (`ele
216236
| Create profile | `CreateProfileAvatar`, `CreateProfileUsername`, `CreateProfileSave` |
217237
| Pay contacts | `PayContactsToggle`, `PayContactsContinue` |
218238
| Profile (view) | `ProfileEdit`, `ProfileCopy`, `ProfileShare`; empty/error: `ProfileRetry`, `ProfileEmptySignOut` (iOS) |
219-
| Profile (presentation) | **`ProfileViewName`**, **`ProfileViewNotes`** (own profile only; `CenteredProfileHeader` passes tags on **Profile** screen). **`QRCode`** — same test id as Receive; pubky is read in E2E via **`getUriFromQRCode()`** in `test/helpers/actions.ts` (shared with receive flows). Links: **`ProfileLinkLabel_0`**, **`ProfileLinkValue_0`**, … (index matches link order). Tags section header: **`ProfileViewTagsHeader`**. Each tag chip text: **`Tag-<tagtext>`** (e.g. `Tag-ere`) on the label `Text` / `BodySSB`. |
239+
| Profile (presentation) | **`ProfileViewName`**, **`ProfileViewNotes`** (own profile only; `CenteredProfileHeader` passes tags on **Profile** screen). **`ProfileQRCode`** is read in E2E via **`getUriFromQRCode()`** in `test/helpers/actions.ts` (shared with receive flows). Links: **`ProfileLinkLabel_0`**, **`ProfileLinkValue_0`**, … (index matches link order). Tags section header: **`ProfileViewTagsHeader`**. Each tag chip text: **`Tag-<tagtext>`** (e.g. `Tag-ere`) on the label `Text` / `BodySSB`. |
220240
| Edit profile | `EditProfileAvatar`, `ProfileEditName`, `ProfileEditBio`, `ProfileEditAddLink`, `ProfileEditLink_0` (URL field), `ProfileEditLinkRemove_0` (trash on that row), …, `ProfileEditAddTag`, remove chip `Tag-<tag>-delete`, `ProfileEditDelete`, **`ProfileEditCancel`**, **`ProfileEditSave`** |
221241
| Add link sheet | `AddLinkLabel`, `AddLinkUrl`, `AddLinkSave`, `AddLinkSuggestions` |
222242
| Add tag sheet | `AddTagInput`, `AddTagSave`, `AddTagSuggestions` |
243+
| Contacts list | `ContactsAddButton`, `ContactsEmptyAddButton`, `ContactsMyProfile`, `Contact_<pubkey>` |
244+
| Add contact | `AddContactPubkyField`, `AddContactPaste`, `AddContactScanQR`, `AddContactAdd`, `AddContactRetry`, `AddContactDiscard`, `AddContactSave` |
245+
| Contact detail | `ContactViewName`, `ContactViewNotes`, `ContactCopy`, `ContactShare`, `ContactEdit`, `ContactDelete` |
246+
| Toasts | `ProfilePubkyCopiedToast`, `ProfileUpdatedToast`, `ContactSavedToast`, `ContactUpdatedToast`, `ContactDeletedToast` |
223247

224248
Ring-only / iOS-only extras (when automating C.x): `PubkyChoiceCancelRing`, `PubkyRingAuthorize`, `PubkyRingCancelAuth`, `PubkyRingDownload`.
225249

226-
Contacts list rows: `ContactsMyProfile`, `Contact_<pubkey>` (existing) — verify in app if still current when adding contact specs.
250+
Contacts intro note: after a profile exists, first opening **Contacts** shows `ContactsIntro`; its `ContactsIntro-button` now behaves as an add-contact entry point. `ContactsEmptyAddButton` appears only after the intro is gone and the user has zero contacts.

test/helpers/profile.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ export async function saveEditContact() {
127127
export const ADD_CONTACT_INVALID_KEY_MESSAGE_SNIPPET = 'Invalid pubky key format';
128128
/** Avoids apostrophe in iOS predicate `CONTAINS` matchers; matches EN copy around "add your own pubky". */
129129
export const ADD_CONTACT_OWN_PUBKY_MESSAGE_SNIPPET = 'add your own pubky';
130+
export const ADD_CONTACT_DUPLICATE_CONTACT_MESSAGE_SNIPPET = 'already in your contacts';
130131

131132
/**
132133
* Opens Contacts → add (+) → enters `pubky` in the sheet and dismisses the keyboard.

test/specs/pubky-profile.e2e.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import {
3232
verifyMyProfileDetails,
3333
verifyPubkyString,
3434
verifyContactDetails,
35+
ADD_CONTACT_DUPLICATE_CONTACT_MESSAGE_SNIPPET,
3536
} from '../helpers/profile';
3637
import { launchFreshApp, reinstallApp } from '../helpers/setup';
3738
import { ciIt } from '../helpers/suite';
@@ -166,6 +167,12 @@ describe('@pubky_profile - Pubky profile', () => {
166167
await verifyContactRowDisplayed(stagingContact.pubky);
167168
}
168169

170+
// try add duplicate contact
171+
// await addContact({ pubky: STAGING_TEST_CONTACTS[0].pubky, firstContact: false });
172+
// await expect(elementById('AddContactAdd')).toBeDisabled();
173+
// await elementByText(ADD_CONTACT_DUPLICATE_CONTACT_MESSAGE_SNIPPET, 'contains').waitForDisplayed();
174+
// await swipeFullScreen('down');
175+
169176
// delete contacts
170177
for (const c of STAGING_TEST_CONTACTS) {
171178
await deleteContact(c.pubky);

0 commit comments

Comments
 (0)