Browse Source

add tests for importing multiple records, fix other confirmation tests

Trent Larson 3 months ago
parent
commit
c5c687a9c5
  1. 12
      src/components/TopMessage.vue
  2. 2
      src/libs/endorserServer.ts
  3. 12
      src/views/AccountViewView.vue
  4. 1
      src/views/ClaimView.vue
  5. 48
      src/views/ConfirmGiftView.vue
  6. 8
      src/views/ContactImportView.vue
  7. 20
      src/views/ContactsView.vue
  8. 5
      src/views/HelpView.vue
  9. 75
      test-playwright/40-add-contact.spec.ts

12
src/components/TopMessage.vue

@ -1,5 +1,15 @@
<template> <template>
<div class="text-center text-red-500">{{ message }}</div> <div class="absolute right-5 top-3">
<span class="align-center text-red-500 mr-2">{{ message }}</span>
<span class="ml-2">
<router-link
:to="{ name: 'help' }"
class="text-xs uppercase bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-1 rounded-md ml-1"
>
Help
</router-link>
</span>
</div>
</template> </template>
<script lang="ts"> <script lang="ts">

2
src/libs/endorserServer.ts

@ -85,6 +85,7 @@ export interface GiveSummaryRecord {
fulfillsPlanHandleId: string; fulfillsPlanHandleId: string;
handleId: string; handleId: string;
issuedAt: string; issuedAt: string;
issuerDid: string;
jwtId: string; jwtId: string;
recipientDid: string; recipientDid: string;
unit: string; unit: string;
@ -98,6 +99,7 @@ export interface OfferSummaryRecord {
fullClaim: OfferVerifiableCredential; fullClaim: OfferVerifiableCredential;
fulfillsPlanHandleId: string; fulfillsPlanHandleId: string;
handleId: string; handleId: string;
issuerDid: string;
jwtId: string; jwtId: string;
nonAmountGivenConfirmed: number; nonAmountGivenConfirmed: number;
objectDescription: string; objectDescription: string;

12
src/views/AccountViewView.vue

@ -22,18 +22,6 @@
<span /> <span />
</div> </div>
<div class="flex justify-between py-2">
<span />
<span>
<router-link
:to="{ name: 'help' }"
class="text-xs uppercase bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-1 rounded-md ml-1"
>
Help
</router-link>
</span>
</div>
<!-- ID notice --> <!-- ID notice -->
<div <div
v-if="!activeDid" v-if="!activeDid"

1
src/views/ClaimView.vue

@ -176,6 +176,7 @@
v-if="libsUtil.isGiveAction(veriClaim)" v-if="libsUtil.isGiveAction(veriClaim)"
:to="'/confirm-gift/' + encodeURIComponent(veriClaim.id)" :to="'/confirm-gift/' + encodeURIComponent(veriClaim.id)"
class="col-span-1 text-blue-500" class="col-span-1 text-blue-500"
data-testId="confirmGiftLink"
> >
Details... Details...
</router-link> </router-link>

48
src/views/ConfirmGiftView.vue

@ -1,5 +1,6 @@
<template> <template>
<QuickNav /> <QuickNav />
<TopMessage />
<!-- CONTENT --> <!-- CONTENT -->
<section id="Content" class="p-6 pb-24 max-w-3xl mx-auto"> <section id="Content" class="p-6 pb-24 max-w-3xl mx-auto">
<!-- Breadcrumb --> <!-- Breadcrumb -->
@ -148,10 +149,10 @@
<span v-if="totalConfirmers() === 0">Nobody has confirmed this.</span> <span v-if="totalConfirmers() === 0">Nobody has confirmed this.</span>
<span v-else-if="totalConfirmers() === 1"> <span v-else-if="totalConfirmers() === 1">
One person has confirmed this. One person confirmed this.
</span> </span>
<span v-else> <span v-else>
{{ totalConfirmers() }} people have confirmed this. {{ totalConfirmers() }} people confirmed this.
</span> </span>
<div v-if="totalConfirmers() > 0"> <div v-if="totalConfirmers() > 0">
@ -170,10 +171,10 @@
" "
> >
<!-- Only show if this person has links to confirmers (below). --> <!-- Only show if this person has links to confirmers (below). -->
Nobody that you know has issued or confirmed this claim. Nobody that you know issued or confirmed this claim.
</div> </div>
<div v-if="confirmerIdList.length > 0"> <div v-if="confirmerIdList.length > 0">
The following people have issued or confirmed this claim. The following people issued or confirmed this claim.
<ul class="ml-4"> <ul class="ml-4">
<li <li
v-for="confirmerId in confirmerIdList" v-for="confirmerId in confirmerIdList"
@ -205,7 +206,7 @@
<!-- <!--
Never need to show this message: Never need to show this message:
"Nobody that you know can see someone who has confirmed this claim." "Nobody that you know can see someone who confirmed this claim."
If there is nobody in the confirmerIdList then we'll show the combined "nobody" message. If there is nobody in the confirmerIdList then we'll show the combined "nobody" message.
If there is somebody in the confirmerIdList then that's all they need to show. If there is somebody in the confirmerIdList then that's all they need to show.
@ -213,7 +214,7 @@
<!-- Now show anyone linked to confirmers. --> <!-- Now show anyone linked to confirmers. -->
<div v-if="confsVisibleToIdList.length > 0"> <div v-if="confsVisibleToIdList.length > 0">
The following people can connect you with people who have issued or The following people can connect you with people who issued or
confirmed this claim. confirmed this claim.
<ul class="ml-4"> <ul class="ml-4">
<li <li
@ -249,10 +250,11 @@
<!-- explain if user cannot confirm --> <!-- explain if user cannot confirm -->
<!-- Note that these conditions are mirrored in userCanConfirm(). --> <!-- Note that these conditions are mirrored in userCanConfirm(). -->
<div v-if="confirmerIdList.includes(activeDid)"> <div v-if="!isRegistered">
You have confirmed this claim. You cannot confirm this because you are not registered. Find someone
to register you, maybe on the Help page.
</div> </div>
<div v-else-if="giveDetails.agentDid == activeDid"> <div v-else-if="giveDetails.issuerDid == activeDid">
You cannot confirm this because you issued this claim, so you already You cannot confirm this because you issued this claim, so you already
count as confirming it. count as confirming it.
</div> </div>
@ -410,13 +412,14 @@ import { Account } from "@/db/tables/accounts";
import { Contact } from "@/db/tables/contacts"; import { Contact } from "@/db/tables/contacts";
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings"; import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
import * as serverUtil from "@/libs/endorserServer"; import * as serverUtil from "@/libs/endorserServer";
import { displayAmount } from "@/libs/endorserServer"; import {displayAmount, GiveSummaryRecord} from "@/libs/endorserServer";
import * as libsUtil from "@/libs/util"; import * as libsUtil from "@/libs/util";
import { isGiveAction } from "@/libs/util"; import { isGiveAction } from "@/libs/util";
import TopMessage from "@/components/TopMessage.vue";
@Component({ @Component({
methods: { displayAmount }, methods: { displayAmount },
components: { QuickNav }, components: { TopMessage, QuickNav },
}) })
export default class ClaimView extends Vue { export default class ClaimView extends Vue {
$notify!: (notification: NotificationIface, timeout?: number) => void; $notify!: (notification: NotificationIface, timeout?: number) => void;
@ -430,7 +433,7 @@ export default class ClaimView extends Vue {
confirmerIdList: string[] = []; // list of DIDs that have confirmed this claim excluding the issuer confirmerIdList: string[] = []; // list of DIDs that have confirmed this claim excluding the issuer
confsVisibleErrorMessage = ""; confsVisibleErrorMessage = "";
confsVisibleToIdList: string[] = []; // list of DIDs that can see any confirmer confsVisibleToIdList: string[] = []; // list of DIDs that can see any confirmer
giveDetails = null; giveDetails?: GiveSummaryRecord;
giverName = ""; giverName = "";
issuerName = ""; issuerName = "";
isLoading = false; isLoading = false;
@ -453,7 +456,7 @@ export default class ClaimView extends Vue {
this.confirmerIdList = []; this.confirmerIdList = [];
this.confsVisibleErrorMessage = ""; this.confsVisibleErrorMessage = "";
this.confsVisibleToIdList = []; this.confsVisibleToIdList = [];
this.giveDetails = null; this.giveDetails = undefined;
this.isRegistered = false; this.isRegistered = false;
this.numConfsNotVisible = 0; this.numConfsNotVisible = 0;
this.urlForNewGive = ""; this.urlForNewGive = "";
@ -605,6 +608,12 @@ export default class ClaimView extends Vue {
}, },
3000, 3000,
); );
return;
}
// the logic already stops earlier if the claim doesn't exist but this helps with typechecking
if (!this.giveDetails) {
return;
} }
this.urlForNewGive = "/gifted-details?"; this.urlForNewGive = "/gifted-details?";
@ -645,7 +654,8 @@ export default class ClaimView extends Vue {
this.giveDetails.fulfillsHandleId this.giveDetails.fulfillsHandleId
) { ) {
this.urlForNewGive += this.urlForNewGive +=
"&offerId=" + encodeURIComponent(this.giveDetails.fulfillsHandleId); "&offerId=" +
encodeURIComponent(this.giveDetails?.fulfillsHandleId as string);
} }
if (this.giveDetails.fulfillsPlanHandleId) { if (this.giveDetails.fulfillsPlanHandleId) {
this.urlForNewGive += this.urlForNewGive +=
@ -666,9 +676,11 @@ export default class ClaimView extends Vue {
const resultList1 = response.data.result || []; const resultList1 = response.data.result || [];
//const publicUrls = resultList.publicUrls || []; //const publicUrls = resultList.publicUrls || [];
delete resultList1.publicUrls; delete resultList1.publicUrls;
// remove any hidden DIDs
const resultList2 = R.reject(serverUtil.isHiddenDid, resultList1); const resultList2 = R.reject(serverUtil.isHiddenDid, resultList1);
// remove confirmations by this user
const resultList3 = R.reject( const resultList3 = R.reject(
(did: string) => did === this.giveDetails.agentDid, (did: string) => did === this.giveDetails?.issuerDid,
resultList2, resultList2,
); );
this.confirmerIdList = resultList3; this.confirmerIdList = resultList3;
@ -814,11 +826,11 @@ export default class ClaimView extends Vue {
group: "alert", group: "alert",
type: "info", type: "info",
title: "Already Confirmed", title: "Already Confirmed",
text: "You have already confirmed this claim.", text: "You already confirmed this claim.",
}, },
3000, 3000,
); );
} else if (this.giveDetails.agentDid == this.activeDid) { } else if (this.giveDetails?.issuerDid == this.activeDid) {
this.$notify( this.$notify(
{ {
group: "alert", group: "alert",
@ -828,7 +840,7 @@ export default class ClaimView extends Vue {
}, },
3000, 3000,
); );
} else if (serverUtil.containsHiddenDid(this.giveDetails.fullClaim)) { } else if (serverUtil.containsHiddenDid(this.giveDetails?.fullClaim)) {
this.$notify( this.$notify(
{ {
group: "alert", group: "alert",

8
src/views/ContactImportView.vue

@ -21,8 +21,12 @@
Contacts. Contacts.
</span> </span>
<div v-if="sameCount > 0"> <div v-if="sameCount > 0">
{{ sameCount }} contact{{ sameCount == 1 ? "" : "s" }} are the same as <span v-if="sameCount == 1"
existing contacts. >One contact is the same as an existing contact</span
>
<span v-else
>{{ sameCount }} contacts are the same as existing contacts</span
>
</div> </div>
<!-- Results List --> <!-- Results List -->

20
src/views/ContactsView.vue

@ -1,5 +1,7 @@
<template> <template>
<QuickNav selected="Contacts"></QuickNav> <QuickNav selected="Contacts" />
<TopMessage />
<section id="Content" class="p-6 pb-24 max-w-3xl mx-auto"> <section id="Content" class="p-6 pb-24 max-w-3xl mx-auto">
<!-- Heading --> <!-- Heading -->
<h1 id="ViewHeading" class="text-4xl text-center font-light pt-4 mb-8"> <h1 id="ViewHeading" class="text-4xl text-center font-light pt-4 mb-8">
@ -41,7 +43,7 @@
</button> </button>
</div> </div>
<div class="flex justify-between"> <div class="flex justify-between" v-if="contacts.length > 0">
<div class="w-full text-left"> <div class="w-full text-left">
<input <input
type="checkbox" type="checkbox"
@ -53,6 +55,7 @@
: (contactsSelected = contacts.map((contact) => contact.did)) : (contactsSelected = contacts.map((contact) => contact.did))
" "
class="align-middle ml-2 h-6 w-6" class="align-middle ml-2 h-6 w-6"
data-testId="contactCheckAllTop"
/> />
<button <button
href="" href=""
@ -64,8 +67,9 @@
" "
@click="copySelectedContacts()" @click="copySelectedContacts()"
v-if="!showGiveNumbers" v-if="!showGiveNumbers"
data-testId="copySelectedContactsButtonTop"
> >
Copy Selected Contacts Copy Selections
</button> </button>
</div> </div>
@ -117,6 +121,7 @@
class="border-b border-slate-300 pt-1 pb-1" class="border-b border-slate-300 pt-1 pb-1"
v-for="contact in filteredContacts()" v-for="contact in filteredContacts()"
:key="contact.did" :key="contact.did"
data-testId="contactListItem"
> >
<div class="grow overflow-hidden"> <div class="grow overflow-hidden">
<div class="flex items-center"> <div class="flex items-center">
@ -140,6 +145,7 @@
: contactsSelected.push(contact.did) : contactsSelected.push(contact.did)
" "
class="ml-2 h-6 w-6" class="ml-2 h-6 w-6"
data-testId="contactCheckOne"
/> />
<h2 class="text-base font-semibold ml-2"> <h2 class="text-base font-semibold ml-2">
@ -226,7 +232,7 @@
</ul> </ul>
<p v-else>There are no contacts.</p> <p v-else>There are no contacts.</p>
<div class="mt-2 w-full text-left"> <div class="mt-2 w-full text-left" v-if="contacts.length > 0">
<input <input
type="checkbox" type="checkbox"
v-if="!showGiveNumbers" v-if="!showGiveNumbers"
@ -237,6 +243,7 @@
: (contactsSelected = contacts.map((contact) => contact.did)) : (contactsSelected = contacts.map((contact) => contact.did))
" "
class="align-middle ml-2 h-6 w-6" class="align-middle ml-2 h-6 w-6"
data-testId="contactCheckAllBottom"
/> />
<button <button
href="" href=""
@ -249,7 +256,7 @@
@click="copySelectedContacts()" @click="copySelectedContacts()"
v-if="!showGiveNumbers" v-if="!showGiveNumbers"
> >
Copy Selected Contacts Copy Selections
</button> </button>
</div> </div>
@ -300,9 +307,10 @@ import QuickNav from "@/components/QuickNav.vue";
import EntityIcon from "@/components/EntityIcon.vue"; import EntityIcon from "@/components/EntityIcon.vue";
import GiftedDialog from "@/components/GiftedDialog.vue"; import GiftedDialog from "@/components/GiftedDialog.vue";
import OfferDialog from "@/components/OfferDialog.vue"; import OfferDialog from "@/components/OfferDialog.vue";
import TopMessage from "@/components/TopMessage.vue";
@Component({ @Component({
components: { GiftedDialog, EntityIcon, OfferDialog, QuickNav }, components: {TopMessage, GiftedDialog, EntityIcon, OfferDialog, QuickNav },
}) })
export default class ContactsView extends Vue { export default class ContactsView extends Vue {
$notify!: (notification: NotificationIface, timeout?: number) => void; $notify!: (notification: NotificationIface, timeout?: number) => void;

5
src/views/HelpView.vue

@ -54,8 +54,9 @@
<h2 class="text-xl font-semibold">How do I get started?</h2> <h2 class="text-xl font-semibold">How do I get started?</h2>
<p> <p>
You need someone to register you, like the person who told you You need someone to register you, like the person who told you
about this app, on the Contacts about this app, on the Contacts <fa icon="users" class="fa-fw" /> page.
<fa icon="users" class="fa-fw" /> page. After they register you, you can If you heard about this from our outreach, feel free to contact us (below) for a chat.
After someone registers you, you can
select any contact on the home page (or "anonymous") and record your select any contact on the home page (or "anonymous") and record your
appreciation for... whatever. The main goal is to record what people appreciation for... whatever. The main goal is to record what people
have given you, to grow giving economies. You can also record your own have given you, to grow giving economies. You can also record your own

75
test-playwright/40-add-contact.spec.ts

@ -21,26 +21,25 @@ test('Add contact, record gift, confirm gift', async ({ page }) => {
const finalTitle = standardTitle + finalRandomString; const finalTitle = standardTitle + finalRandomString;
// Contact name // Contact name
const contactName = 'Contact #000'; const contactName = 'Contact #111';
// Import user 01 // Import user 01
await importUser(page, '01'); await importUser(page, '01');
// Add new contact 00 // Add new contact
await page.goto('./contacts'); await page.goto('./contacts');
await page.getByPlaceholder('URL or DID, Name, Public Key').fill('did:ethr:0x0000694B58C2cC69658993A90D3840C560f2F51F, User #000'); await page.getByPlaceholder('URL or DID, Name, Public Key').fill('did:ethr:0x0000694B58C2cC69658993A90D3840C560f2F51F, User #000');
await page.locator('button > svg.fa-plus').click(); await page.locator('button > svg.fa-plus').click();
await expect(page.locator('div[role="alert"]')).toBeVisible(); await expect(page.locator('div[role="alert"]')).toBeVisible();
// Why doesn't the alert box come up every time? await page.locator('div[role="alert"] button:has-text("Yes")').click();
// await page.locator('div[role="alert"] button:has-text("Yes")').click();
// Verify added contact // Verify added contact
await expect(page.locator('li.border-b')).toContainText('User #000'); await expect(page.locator('li.border-b')).toContainText('User #000');
// Rename contact // Rename contact
await page.locator('li.border-b div div > a[title="See more about this person"]').click(); await page.locator('li.border-b div div > a[title="See more about this person"]').click();
await page.locator('h2 > button[title="Edit"]').click(); await page.locator('h2 > button > svg.fa-pen').click();
await expect(page.locator('div.dialog-overlay > div.dialog').filter({ hasText: 'Edit Name' })).toBeVisible(); await expect(page.locator('div.dialog-overlay > div.dialog').filter({ hasText: 'Edit Name' })).toBeVisible();
await page.getByPlaceholder('Name', { exact: true }).fill(contactName); await page.getByPlaceholder('Name', { exact: true }).fill(contactName);
await page.locator('.dialog > .flex > button').first().click(); await page.locator('.dialog > .flex > button').first().click();
@ -76,11 +75,73 @@ test('Add contact, record gift, confirm gift', async ({ page }) => {
await page.locator('li').filter({ hasText: finalTitle }).locator('a').click(); await page.locator('li').filter({ hasText: finalTitle }).locator('a').click();
// Confirm gift as user 00 // Confirm gift as user 00
await page.getByTestId('confirmGiftLink').click();
await page.getByRole('button', { name: 'Confirm' }).click(); await page.getByRole('button', { name: 'Confirm' }).click();
await page.getByRole('button', { name: 'Yes' }).click(); await page.getByRole('button', { name: 'Yes' }).click();
await expect(page.getByText('Confirmation submitted.')).toBeVisible(); await expect(page.getByText('Confirmation submitted.')).toBeVisible();
// Refresh claim page, Confirm button should be hidden // Refresh claim page, Confirm button should throw an alert because they already confirmed
await page.reload(); await page.reload();
await expect(page.getByRole('button', { name: 'Confirm' })).toBeHidden(); await page.getByRole('button', { name: 'Confirm' }).click();
await expect(page.locator('div[role="alert"]')).toBeVisible();
});
test('Add contact, copy details, delete, and import various ways', async ({ page, context }) => {
await importUser(page, '00');
// Add new contact
await page.goto('./contacts');
await page.getByPlaceholder('URL or DID, Name, Public Key').fill('did:ethr:0x111d15564f824D56C7a07b913aA7aDd03382aA39, User #111');
await page.locator('button > svg.fa-plus').click();
await expect(page.locator('div[role="alert"]')).toBeVisible();
await page.locator('div[role="alert"] button:has-text("No")').click();
await page.locator('div[role="alert"] button > svg.fa-xmark').click();
// wait for the alert to disappear
await expect(page.locator('div[role="alert"]')).toBeHidden();
// Add another new contact
await page.getByPlaceholder('URL or DID, Name, Public Key').fill('did:ethr:0x222BB77E6Ff3774d34c751f3c1260866357B677b, User #222, asdf1234');
await page.locator('button > svg.fa-plus').click();
await expect(page.locator('div[role="alert"]')).toBeVisible();
await page.locator('div[role="alert"] button:has-text("No")').click();
await page.locator('div[role="alert"] button > svg.fa-xmark').click();
await expect(page.locator('div[role="alert"]')).toBeHidden();
await expect(page.getByTestId('contactListItem')).toHaveCount(2);
//// Copy contact details, export them, remove them, and paste to add them
// Copy contact details
await page.getByTestId('contactCheckAllTop').click();
await page.getByTestId('copySelectedContactsButtonTop').click();
await page.locator('div[role="alert"] button > svg.fa-xmark').click(); // dismiss alert
// I would prefer to copy from the clipboard, but the recommended approaches don't work.
// this seems to fail in non-chromium browsers
//await context.grantPermissions(['clipboard-read', 'clipboard-write']);
// this seems to fail in chromium (at least) where clipboard is undefined
//const contactData = await navigator.clipboard.readText();
// see contact details on the second contact
await page.getByTestId('contactListItem').nth(1).locator('a').click();
// remove contact
await page.locator('button > svg.fa-trash-can').click();
await page.locator('div[role="alert"] button:has-text("Yes")').click();
// for some reason, .isHidden() (without expect) doesn't work
await expect(page.locator('div[role="alert"] button:has-text("Yes")')).toBeHidden();
await page.locator('div[role="alert"] button > svg.fa-xmark').click(); // dismiss alert
// go to the contacts page and paste the copied contact details
await page.goto('./contacts');
// check that there are fewer contacts
await expect(page.getByTestId('contactListItem')).toHaveCount(1);
const contactData = 'Paste this: [{ "did": "did:ethr:0x111d15564f824D56C7a07b913aA7aDd03382aA39", "name": "User #111" }, { "did": "did:ethr:0x222BB77E6Ff3774d34c751f3c1260866357B677b", "name": "User #222", "publicKeyBase64": "asdf1234"}] '
await page.getByPlaceholder('URL or DID, Name, Public Key').fill(contactData);
await page.locator('button > svg.fa-plus').click();
// we're on the contact-import page
await expect(page.locator('li', { hasText: 'New' })).toHaveCount(1);
await expect(page.locator('span').filter({ hasText: 'the same as' })).toBeVisible();
await page.locator('button', { hasText: 'Import' }).click();
// check that there are more contacts
await expect(page.getByTestId('contactListItem')).toHaveCount(2);
}); });

Loading…
Cancel
Save