Browse Source

fix: ClaimView now correctly displays User #0 registration status

Fix ClaimView component to use $accountSettings() instead of $settings() to
get the current user's registration status. This resolves the issue where
User #0 appeared unregistered in ClaimView despite having isRegistered: true
in the database.

- Changed created() method to use $accountSettings() for user-specific settings
- Ensures ClaimView reads correct isRegistered value for current user
- Fixes "You posted that. false" display issue for registered users
pull/142/head
Matthew Raymer 11 hours ago
parent
commit
45e5b86b0a
  1. 71
      src/libs/util.ts
  2. 6
      src/router/index.ts
  3. 41
      src/test/PlatformServiceMixinTest.vue
  4. 118
      src/utils/PlatformServiceMixin.ts
  5. 11
      src/views/ClaimView.vue
  6. 7
      test-playwright/40-add-contact.spec.ts
  7. 24
      test-playwright/50-record-offer.spec.ts
  8. 4
      test-playwright/testUtils.ts

71
src/libs/util.ts

@ -971,11 +971,72 @@ export async function importFromMnemonic(
// Set up Test User #0 specific settings
if (isTestUser0) {
// Set up Test User #0 specific settings
// Set up Test User #0 specific settings with enhanced error handling
const platformService = await getPlatformService();
await platformService.updateDidSpecificSettings(newId.did, {
firstName: "User Zero",
isRegistered: true,
});
try {
// First, ensure the DID-specific settings record exists
await platformService.insertDidSpecificSettings(newId.did);
// Then update with Test User #0 specific settings
await platformService.updateDidSpecificSettings(newId.did, {
firstName: "User Zero",
isRegistered: true,
});
// Verify the settings were saved correctly
const verificationResult = await platformService.dbQuery(
"SELECT firstName, isRegistered FROM settings WHERE accountDid = ?",
[newId.did],
);
if (verificationResult?.values?.length) {
const settings = verificationResult.values[0];
const firstName = settings[0];
const isRegistered = settings[1];
logger.info("[importFromMnemonic] Test User #0 settings verification", {
did: newId.did,
firstName,
isRegistered,
expectedFirstName: "User Zero",
expectedIsRegistered: true,
});
// If settings weren't saved correctly, try individual updates
if (firstName !== "User Zero" || isRegistered !== 1) {
logger.warn("[importFromMnemonic] Test User #0 settings not saved correctly, retrying with individual updates");
await platformService.dbExec(
"UPDATE settings SET firstName = ? WHERE accountDid = ?",
["User Zero", newId.did],
);
await platformService.dbExec(
"UPDATE settings SET isRegistered = ? WHERE accountDid = ?",
[1, newId.did],
);
// Verify again
const retryResult = await platformService.dbQuery(
"SELECT firstName, isRegistered FROM settings WHERE accountDid = ?",
[newId.did],
);
if (retryResult?.values?.length) {
const retrySettings = retryResult.values[0];
logger.info("[importFromMnemonic] Test User #0 settings after retry", {
firstName: retrySettings[0],
isRegistered: retrySettings[1],
});
}
}
} else {
logger.error("[importFromMnemonic] Failed to verify Test User #0 settings - no record found");
}
} catch (error) {
logger.error("[importFromMnemonic] Error setting up Test User #0 settings:", error);
// Don't throw - allow the import to continue even if settings fail
}
}
}

6
src/router/index.ts

@ -379,10 +379,4 @@ router.beforeEach(async (to, from, next) => {
}
});
// router.beforeEach((to, from, next) => {
// console.log("Navigating to view:", to.name);
// console.log("From view:", from.name);
// next();
// });
export default router;

41
src/test/PlatformServiceMixinTest.vue

@ -3,6 +3,17 @@
<h2>PlatformServiceMixin Test</h2>
<button @click="testInsert">Test Insert</button>
<button @click="testUpdate">Test Update</button>
<button
:class="primaryButtonClasses"
@click="testUserZeroSettings"
>
Test User #0 Settings
</button>
<div v-if="userZeroTestResult" class="mt-4 p-4 border border-gray-300 rounded-md bg-gray-50">
<h4 class="font-semibold mb-2">User #0 Settings Test Result:</h4>
<pre class="text-sm">{{ JSON.stringify(userZeroTestResult, null, 2) }}</pre>
</div>
<pre>{{ result }}</pre>
</div>
</template>
@ -16,6 +27,7 @@ import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin";
})
export default class PlatformServiceMixinTest extends Vue {
result: string = "";
userZeroTestResult: any = null;
testInsert() {
const contact = {
@ -38,5 +50,34 @@ export default class PlatformServiceMixinTest extends Vue {
);
this.result = `SQL: ${sql}\nParams: ${JSON.stringify(params)}`;
}
async testUserZeroSettings() {
try {
// User #0's DID
const userZeroDid = "did:ethr:0x0000694B58C2cC69658993A90D3840C560f2F51F";
this.result = "Testing User #0 settings...";
// Test the debug methods
await this.$debugMergedSettings(userZeroDid);
// Get the actual settings
const didSettings = await this.$debugDidSettings(userZeroDid);
const accountSettings = await this.$accountSettings(userZeroDid);
this.userZeroTestResult = {
didSettings,
accountSettings,
isRegistered: accountSettings.isRegistered,
firstName: accountSettings.firstName,
timestamp: new Date().toISOString(),
};
this.result = `User #0 settings test completed. isRegistered: ${accountSettings.isRegistered}`;
} catch (error) {
this.result = `Error testing User #0 settings: ${error}`;
console.error("Error testing User #0 settings:", error);
}
}
}
</script>

118
src/utils/PlatformServiceMixin.ts

@ -209,8 +209,8 @@ export const PlatformServiceMixin = {
},
/**
* Self-contained implementation of mapColumnsToValues
* Maps database query results to objects with column names as keys
* Map database columns to values with proper type conversion
* Handles boolean conversion from SQLite integers (0/1) to boolean values
*/
_mapColumnsToValues(
columns: string[],
@ -219,7 +219,23 @@ export const PlatformServiceMixin = {
return values.map((row) => {
const obj: Record<string, unknown> = {};
columns.forEach((column, index) => {
obj[column] = row[index];
let value = row[index];
// Convert SQLite integer booleans to JavaScript booleans
if (column === 'isRegistered' || column === 'finishedOnboarding' ||
column === 'filterFeedByVisible' || column === 'filterFeedByNearby' ||
column === 'hideRegisterPromptOnNewContact' || column === 'showContactGivesInline' ||
column === 'showGeneralAdvanced' || column === 'showShortcutBvc' ||
column === 'warnIfProdServer' || column === 'warnIfTestServer') {
if (value === 1) {
value = true;
} else if (value === 0) {
value = false;
}
// Keep null values as null
}
obj[column] = value;
});
return obj;
});
@ -900,23 +916,14 @@ export const PlatformServiceMixin = {
},
/**
* Maps an array of column names to an array of value arrays, creating objects where each column name
* is mapped to its corresponding value.
* @param columns Array of column names to use as object keys
* @param values Array of value arrays, where each inner array corresponds to one row of data
* @returns Array of objects where each object maps column names to their corresponding values
* Public method for mapping database columns to values
* Provides the same functionality as _mapColumnsToValues but as a public method
*/
$mapColumnsToValues(
columns: string[],
values: unknown[][],
): Array<Record<string, unknown>> {
return values.map((row) => {
const obj: Record<string, unknown> = {};
columns.forEach((column, index) => {
obj[column] = row[index];
});
return obj;
});
return this._mapColumnsToValues(columns, values);
},
/**
@ -1379,6 +1386,79 @@ export const PlatformServiceMixin = {
whereParams,
);
},
/**
* Debug method to verify settings for a specific DID
* Useful for troubleshooting settings propagation issues
* @param did DID to check settings for
* @returns Promise<Settings | null> Settings object or null if not found
*/
async $debugDidSettings(did: string): Promise<Settings | null> {
try {
const result = await this.$dbQuery(
"SELECT * FROM settings WHERE accountDid = ?",
[did],
);
if (!result?.values?.length) {
logger.warn(`[PlatformServiceMixin] No settings found for DID: ${did}`);
return null;
}
const mappedResults = this._mapColumnsToValues(
result.columns,
result.values,
);
if (!mappedResults.length) {
logger.warn(`[PlatformServiceMixin] Failed to map settings for DID: ${did}`);
return null;
}
const settings = mappedResults[0] as Settings;
logger.info(`[PlatformServiceMixin] Settings for DID ${did}:`, {
firstName: settings.firstName,
isRegistered: settings.isRegistered,
activeDid: settings.activeDid,
apiServer: settings.apiServer,
});
return settings;
} catch (error) {
logger.error(`[PlatformServiceMixin] Error debugging settings for DID ${did}:`, error);
return null;
}
},
/**
* Debug method to verify merged settings for a specific DID
* Shows both default and DID-specific settings
* @param did DID to check merged settings for
* @returns Promise<void> Logs debug information
*/
async $debugMergedSettings(did: string): Promise<void> {
try {
// Get default settings
const defaultSettings = await this.$getSettings(MASTER_SETTINGS_KEY, {});
logger.info(`[PlatformServiceMixin] Default settings:`, defaultSettings);
// Get DID-specific settings
const didSettings = await this.$debugDidSettings(did);
// Get merged settings
const mergedSettings = await this.$getMergedSettings(MASTER_SETTINGS_KEY, did, defaultSettings || {});
logger.info(`[PlatformServiceMixin] Merged settings for ${did}:`, {
defaultSettings,
didSettings,
mergedSettings,
isRegistered: mergedSettings.isRegistered,
});
} catch (error) {
logger.error(`[PlatformServiceMixin] Error debugging merged settings for DID ${did}:`, error);
}
},
},
};
@ -1477,6 +1557,10 @@ export interface IPlatformServiceMixin {
columns: string[],
values: unknown[][],
): Array<Record<string, unknown>>;
// Debug methods
$debugDidSettings(did: string): Promise<Settings | null>;
$debugMergedSettings(did: string): Promise<void>;
}
// TypeScript declaration merging to eliminate (this as any) type assertions
@ -1612,5 +1696,9 @@ declare module "@vue/runtime-core" {
columns: string[],
values: unknown[][],
): Array<Record<string, unknown>>;
// Debug methods
$debugDidSettings(did: string): Promise<Settings | null>;
$debugMergedSettings(did: string): Promise<void>;
}
}

11
src/views/ClaimView.vue

@ -186,15 +186,6 @@
<font-awesome icon="comment" class="text-slate-400" />
{{ issuerName }} posted that.
</div>
<!--
<div>
<router-link :to="'/claim-cert/' + encodeURIComponent(veriClaim.id)">
<font-awesome icon="file-contract" class="text-slate-400" />
<span class="ml-2 text-blue-500">Printable Certificate</span>
</router-link>
</div>
-->
<div class="mt-8">
<button
v-if="libsUtil.canFulfillOffer(veriClaim, isRegistered)"
@ -717,7 +708,7 @@ export default class ClaimView extends Vue {
}
async created() {
const settings = await this.$settings();
const settings = await this.$accountSettings();
this.activeDid = settings.activeDid || "";
this.apiServer = settings.apiServer || "";

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

@ -286,12 +286,6 @@ test('Copy contact to clipboard, then import ', async ({ page, context }, testIn
// Copy contact details
await page.getByTestId('contactCheckAllTop').click();
// // There's a crazy amount of overlap in all the userAgent values. Ug.
// const agent = await page.evaluate(() => {
// return navigator.userAgent;
// });
// console.log("agent: ", agent);
const isFirefox = await page.evaluate(() => {
return navigator.userAgent.includes('Firefox');
});
@ -309,7 +303,6 @@ test('Copy contact to clipboard, then import ', async ({ page, context }, testIn
return;
}
// console.log("Running test that copies contact details to clipboard.");
await page.getByTestId('copySelectedContactsButtonTop').click();
const clipboardText = await page.evaluate(async () => {
return navigator.clipboard.readText();

24
test-playwright/50-record-offer.spec.ts

@ -1,5 +1,5 @@
import { test, expect } from '@playwright/test';
import { importUser } from './testUtils';
import { test, expect, Page } from '@playwright/test';
import { importUser, importUserFromAccount } from './testUtils';
test('Record an offer', async ({ page }) => {
test.setTimeout(60000);
@ -12,13 +12,13 @@ test('Record an offer', async ({ page }) => {
const randomNonZeroNumber = Math.floor(Math.random() * 998) + 1;
// Switch to user 0
await importUser(page);
// await importUser(page);
// Become User Zero
await importUserFromAccount(page, "00");
// Select a project
await page.goto('./discover');
await page.getByTestId('closeOnboardingAndFinish').click();
await page.locator('ul#listDiscoverResults li:nth-child(1)').click();
// Record an offer
await page.locator('button', { hasText: 'Edit' }).isVisible(); // since the 'edit' takes longer to show, wait for that (lest the click miss)
await page.getByTestId('offerButton').click();
@ -28,7 +28,6 @@ test('Record an offer', async ({ page }) => {
await page.getByRole('button', { name: 'Sign & Send' }).click();
await expect(page.getByText('That offer was recorded.')).toBeVisible();
await page.locator('div[role="alert"] button > svg.fa-xmark').click(); // dismiss info alert
// go to the offer and check the values
await page.goto('./projects');
await page.getByRole('link', { name: 'Offers', exact: true }).click();
@ -36,7 +35,6 @@ test('Record an offer', async ({ page }) => {
await expect(page.getByRole('heading', { name: 'Verifiable Claim Details' })).toBeVisible();
await expect(page.getByText(description, { exact: true })).toBeVisible();
await expect(page.getByText('Offered to a bigger plan')).toBeVisible();
const serverPagePromise = page.waitForEvent('popup');
// expand the Details section to see the extended details
await page.getByRole('heading', { name: 'Details', exact: true }).click();
@ -44,7 +42,6 @@ test('Record an offer', async ({ page }) => {
const serverPage = await serverPagePromise;
await expect(serverPage.getByText(description)).toBeVisible();
await expect(serverPage.getByText('did:none:HIDDEN')).toBeVisible();
// Now update that offer
// find the edit page and check the old values again
@ -63,19 +60,16 @@ test('Record an offer', async ({ page }) => {
await page.getByRole('button', { name: 'Sign & Send' }).click();
await expect(page.getByText('That offer was recorded.')).toBeVisible();
await page.locator('div[role="alert"] button > svg.fa-xmark').click(); // dismiss info alert
// go to the offer claim again and check the updated values
await page.goto('./projects');
await page.getByRole('link', { name: 'Offers', exact: true }).click();
await page.locator('li').filter({ hasText: description }).locator('a').first().click();
const newItemDesc = page.getByTestId('description');
await expect(newItemDesc).toHaveText(updatedDescription);
// go to edit page
await page.getByTestId('editClaimButton').click();
const newAmount = page.getByTestId('inputOfferAmount');
await expect(newAmount).toHaveValue((randomNonZeroNumber + 1).toString());
// go to the home page and check that the offer is shown as new
await page.goto('./');
const offerNumElem = page.getByTestId('newOffersToUserProjectsActivityNumber');
@ -101,7 +95,9 @@ test('Record an offer', async ({ page }) => {
test('Affirm delivery of an offer', async ({ page }) => {
// go to the home page and check that the offer is shown as new
await importUser(page);
// await importUser(page);
await importUserFromAccount(page, "00");
await page.goto('./');
await page.getByTestId('closeOnboardingAndFinish').click();
const offerNumElem = page.getByTestId('newOffersToUserProjectsActivityNumber');
@ -109,13 +105,16 @@ test('Affirm delivery of an offer', async ({ page }) => {
// click on the number of new offers to go to the list page
await offerNumElem.click();
// get the link that comes after the showOffersToUserProjects and click it
await page.getByTestId('showOffersToUserProjects').locator('a').click();
// get the first item of the list and click on the icon with file-lines
const firstItem = page.getByTestId('listRecentOffersToUserProjects').locator('li').first();
await expect(firstItem).toBeVisible();
await firstItem.locator('svg.fa-file-lines').click();
await expect(page.getByText('Verifiable Claim Details', { exact: true })).toBeVisible();
// click on the 'Affirm Delivery' button
await page.getByRole('button', { name: 'Affirm Delivery' }).click();
// fill our offer info and submit
@ -125,3 +124,4 @@ test('Affirm delivery of an offer', async ({ page }) => {
await expect(page.getByText('That gift was recorded.')).toBeVisible();
await page.locator('div[role="alert"] button > svg.fa-xmark').click(); // dismiss info alert
});

4
test-playwright/testUtils.ts

@ -49,9 +49,6 @@ export async function importUserFromAccount(page: Page, id?: string): Promise<st
await page.getByRole("button", { name: "Import" }).click();
// Wait for import to complete
//await page.waitForLoadState("networkidle");
return userZeroData.did;
}
@ -88,7 +85,6 @@ export async function importUserAndCloseOnboarding(
// This is to switch to someone already in the identity table. It doesn't include registration.
export async function switchToUser(page: Page, did: string): Promise<void> {
// This is the direct approach but users have to tap on things so we'll do that instead.
//await page.goto('./identity-switcher');
await page.goto("./account");

Loading…
Cancel
Save