forked from trent_larson/crowd-funder-for-time-pwa
feat(ios-testing): Enhance deeplink testing and error handling
- Improve test data generation and validation - Add detailed logging of generated test data - Implement robust validation of required fields - Use ts-node script for test data generation - Add fallback data generation with validation - Enhance deeplink testing UX - Add interactive prompts between tests - Display detailed test progress and next steps - Improve error handling and test skip logic - Add comprehensive logging throughout test execution - Improve DeepLinkErrorView - Add detailed error information display - Show debug information for parameters and queries - Enhance UI with better styling and layout - Add safe area spacing for iOS - Refactor deeplink handling - Standardize route definitions - Improve parameter validation - Add better error logging
This commit is contained in:
1120
package-lock.json
generated
1120
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -40,7 +40,21 @@
|
||||
|
||||
const { execSync } = require('child_process');
|
||||
const { join } = require('path');
|
||||
const { existsSync, mkdirSync, appendFileSync, readFileSync, writeFileSync } = require('fs');
|
||||
const { existsSync, mkdirSync, appendFileSync, readFileSync, writeFileSync, readdirSync, statSync, accessSync } = require('fs');
|
||||
const readline = require('readline');
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout
|
||||
});
|
||||
const { constants } = require('fs');
|
||||
|
||||
const question = (prompt) => new Promise((resolve) => rl.question(prompt, resolve));
|
||||
|
||||
// Make sure to close readline at the end
|
||||
process.on('SIGINT', () => {
|
||||
rl.close();
|
||||
process.exit();
|
||||
});
|
||||
|
||||
// Format date as YYYY-MM-DD-HHMMSS
|
||||
const getLogFileName = () => {
|
||||
@@ -316,122 +330,84 @@ const verifyXcodeInstallation = (log) => {
|
||||
|
||||
// Generate test data using generate_data.ts
|
||||
const generateTestData = async (log) => {
|
||||
log('🔄 Generating test data...');
|
||||
log('\n🔍 DEBUG: Starting test data generation...');
|
||||
|
||||
// Check if test-scripts directory exists
|
||||
if (!existsSync('test-scripts')) {
|
||||
log('⚠️ test-scripts directory not found');
|
||||
log('⚠️ Current directory: ' + process.cwd());
|
||||
|
||||
// List directories to help debug
|
||||
const { readdirSync } = require('fs');
|
||||
log('📂 Directories in current path:');
|
||||
try {
|
||||
const files = readdirSync('.');
|
||||
files.forEach(file => {
|
||||
const isDir = existsSync(file) && require('fs').statSync(file).isDirectory();
|
||||
log(`${isDir ? '📁' : '📄'} ${file}`);
|
||||
});
|
||||
} catch (err) {
|
||||
log(`⚠️ Error listing directory: ${err.message}`);
|
||||
}
|
||||
} else {
|
||||
log('✅ Found test-scripts directory');
|
||||
|
||||
// Check if generate_data.ts exists
|
||||
if (existsSync('test-scripts/generate_data.ts')) {
|
||||
log('✅ Found generate_data.ts');
|
||||
} else {
|
||||
log('⚠️ generate_data.ts not found in test-scripts directory');
|
||||
|
||||
// List files in test-scripts to help debug
|
||||
const { readdirSync } = require('fs');
|
||||
log('📂 Files in test-scripts:');
|
||||
try {
|
||||
const files = readdirSync('test-scripts');
|
||||
files.forEach(file => {
|
||||
log(`📄 ${file}`);
|
||||
});
|
||||
} catch (err) {
|
||||
log(`⚠️ Error listing test-scripts: ${err.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Check directory structure
|
||||
log('📁 Current directory:', process.cwd());
|
||||
log('📁 Directory contents:', require('fs').readdirSync('.'));
|
||||
|
||||
// Create .generated directory if it doesn't exist
|
||||
if (!existsSync('.generated')) {
|
||||
log('📁 Creating .generated directory');
|
||||
mkdirSync('.generated', { recursive: true });
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
// Try to generate test data using the script
|
||||
log('🔄 Running test data generation script...');
|
||||
log('🔄 Attempting to run generate_data.ts...');
|
||||
execSync('npx ts-node test-scripts/generate_data.ts', { stdio: 'inherit' });
|
||||
log('✅ Test data generation script completed');
|
||||
log('✅ Test data generation completed');
|
||||
|
||||
// Verify the generated files exist
|
||||
// Verify and log generated files content
|
||||
const requiredFiles = [
|
||||
'.generated/test-env.json',
|
||||
'.generated/claim_details.json',
|
||||
'.generated/contacts.json'
|
||||
];
|
||||
|
||||
log('🔍 Verifying generated files:');
|
||||
log('\n📝 Verifying generated files:');
|
||||
for (const file of requiredFiles) {
|
||||
if (!existsSync(file)) {
|
||||
log(`⚠️ Required file ${file} was not generated`);
|
||||
throw new Error(`Required file ${file} was not generated`);
|
||||
log(`❌ Missing file: ${file}`);
|
||||
} else {
|
||||
log(`✅ ${file} exists`);
|
||||
const content = readFileSync(file, 'utf8');
|
||||
log(`\n📄 Content of ${file}:`);
|
||||
log(content);
|
||||
try {
|
||||
const parsed = JSON.parse(content);
|
||||
if (file.includes('test-env.json')) {
|
||||
log('🔑 CONTACT1_DID in test-env:', parsed.CONTACT1_DID);
|
||||
}
|
||||
if (file.includes('contacts.json')) {
|
||||
log('👥 First contact DID:', parsed[0]?.did);
|
||||
}
|
||||
} catch (e) {
|
||||
log(`❌ Error parsing ${file}:`, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
log(`⚠️ Failed to generate test data: ${error.message}`);
|
||||
log(`\n⚠️ Test data generation failed: ${error.message}`);
|
||||
log('⚠️ Creating fallback test data...');
|
||||
|
||||
// Create minimal fallback test data
|
||||
// Create fallback data with detailed logging
|
||||
const fallbackTestEnv = {
|
||||
"CONTACT1_DID": "did:example:123456789",
|
||||
"CONTACT1_DID": "did:ethr:0x35A71Ac3fA0A4D5a4903f10F0f7A3ac4034FaB5B",
|
||||
"APP_URL": "https://app.timesafari.example"
|
||||
};
|
||||
|
||||
const fallbackClaimDetails = {
|
||||
"claim_id": "claim_12345",
|
||||
"title": "Test Claim",
|
||||
"description": "This is a test claim"
|
||||
};
|
||||
|
||||
const fallbackContacts = [
|
||||
{
|
||||
"id": "contact1",
|
||||
"name": "Test Contact",
|
||||
"did": "did:example:123456789"
|
||||
"did": "did:ethr:0x35A71Ac3fA0A4D5a4903f10F0f7A3ac4034FaB5B"
|
||||
}
|
||||
];
|
||||
|
||||
// Use writeFileSync to overwrite any existing files
|
||||
const { writeFileSync } = require('fs');
|
||||
log('\n📝 Writing fallback data:');
|
||||
log('TestEnv:', JSON.stringify(fallbackTestEnv, null, 2));
|
||||
log('Contacts:', JSON.stringify(fallbackContacts, null, 2));
|
||||
|
||||
writeFileSync('.generated/test-env.json', JSON.stringify(fallbackTestEnv, null, 2));
|
||||
writeFileSync('.generated/claim_details.json', JSON.stringify(fallbackClaimDetails, null, 2));
|
||||
writeFileSync('.generated/contacts.json', JSON.stringify(fallbackContacts, null, 2));
|
||||
|
||||
log('✅ Fallback test data created');
|
||||
|
||||
// Verify files were created
|
||||
const requiredFiles = [
|
||||
'.generated/test-env.json',
|
||||
'.generated/claim_details.json',
|
||||
'.generated/contacts.json'
|
||||
];
|
||||
|
||||
log('🔍 Verifying fallback files:');
|
||||
for (const file of requiredFiles) {
|
||||
if (!existsSync(file)) {
|
||||
log(`⚠️ Failed to create ${file}`);
|
||||
} else {
|
||||
log(`✅ Created ${file}`);
|
||||
}
|
||||
// Verify fallback data was written
|
||||
log('\n🔍 Verifying fallback data:');
|
||||
try {
|
||||
const writtenTestEnv = JSON.parse(readFileSync('.generated/test-env.json', 'utf8'));
|
||||
const writtenContacts = JSON.parse(readFileSync('.generated/contacts.json', 'utf8'));
|
||||
log('Written TestEnv:', writtenTestEnv);
|
||||
log('Written Contacts:', writtenContacts);
|
||||
} catch (e) {
|
||||
log('❌ Error verifying fallback data:', e);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -535,6 +511,96 @@ const runIosApp = async (log, simulator) => {
|
||||
log('✅ App launched successfully');
|
||||
};
|
||||
|
||||
const validateTestData = (log) => {
|
||||
log('\n=== VALIDATING TEST DATA ===');
|
||||
|
||||
const generateFreshTestData = () => {
|
||||
log('\n🔄 Generating fresh test data...');
|
||||
try {
|
||||
// Ensure .generated directory exists
|
||||
if (!existsSync('.generated')) {
|
||||
mkdirSync('.generated', { recursive: true });
|
||||
}
|
||||
|
||||
// Execute the generate_data.ts script synchronously
|
||||
log('Running generate_data.ts...');
|
||||
execSync('npx ts-node test-scripts/generate_data.ts', {
|
||||
stdio: 'inherit',
|
||||
encoding: 'utf8'
|
||||
});
|
||||
|
||||
// Read and validate the generated files
|
||||
const testEnvPath = '.generated/test-env.json';
|
||||
const contactsPath = '.generated/contacts.json';
|
||||
|
||||
if (!existsSync(testEnvPath) || !existsSync(contactsPath)) {
|
||||
throw new Error('Generated files not found after running generate_data.ts');
|
||||
}
|
||||
|
||||
const testEnv = JSON.parse(readFileSync(testEnvPath, 'utf8'));
|
||||
const contacts = JSON.parse(readFileSync(contactsPath, 'utf8'));
|
||||
|
||||
// Validate required fields
|
||||
if (!testEnv.CONTACT1_DID) {
|
||||
throw new Error('CONTACT1_DID missing from generated test data');
|
||||
}
|
||||
|
||||
log('Generated test data:', {
|
||||
testEnv: testEnv,
|
||||
contacts: contacts
|
||||
});
|
||||
|
||||
return { testEnv, contacts };
|
||||
} catch (error) {
|
||||
log('❌ Test data generation failed:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
// Try to read existing data or generate fresh data
|
||||
const testEnvPath = '.generated/test-env.json';
|
||||
const contactsPath = '.generated/contacts.json';
|
||||
|
||||
let testData;
|
||||
|
||||
// If either file is missing or invalid, generate fresh data
|
||||
if (!existsSync(testEnvPath) || !existsSync(contactsPath)) {
|
||||
testData = generateFreshTestData();
|
||||
} else {
|
||||
try {
|
||||
const testEnv = JSON.parse(readFileSync(testEnvPath, 'utf8'));
|
||||
const contacts = JSON.parse(readFileSync(contactsPath, 'utf8'));
|
||||
|
||||
// Validate required fields
|
||||
if (!testEnv.CLAIM_ID || !testEnv.CONTACT1_DID) {
|
||||
log('⚠️ Existing test data missing required fields, regenerating...');
|
||||
testData = generateFreshTestData();
|
||||
} else {
|
||||
testData = { testEnv, contacts };
|
||||
}
|
||||
} catch (error) {
|
||||
log('⚠️ Error reading existing test data, regenerating...');
|
||||
testData = generateFreshTestData();
|
||||
}
|
||||
}
|
||||
|
||||
// Final validation of data
|
||||
if (!testData.testEnv.CLAIM_ID || !testData.testEnv.CONTACT1_DID) {
|
||||
throw new Error('Test data validation failed even after generation');
|
||||
}
|
||||
|
||||
log('✅ Test data validated successfully');
|
||||
log('📄 Test Environment:', JSON.stringify(testData.testEnv, null, 2));
|
||||
|
||||
return testData;
|
||||
|
||||
} catch (error) {
|
||||
log(`❌ Test data validation failed: ${error.message}`);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Run deeplink tests
|
||||
* Optionally tests deeplinks if the test data is available
|
||||
@@ -543,77 +609,66 @@ const runIosApp = async (log, simulator) => {
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
const runDeeplinkTests = async (log) => {
|
||||
log('🔗 Starting deeplink tests...');
|
||||
log('\n=== Starting Deeplink Tests ===');
|
||||
|
||||
// Import readline module for user input
|
||||
const readline = require('readline');
|
||||
|
||||
// Create readline interface
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout
|
||||
});
|
||||
|
||||
// Promisify the question method
|
||||
const question = (query) => new Promise(resolve => rl.question(query, resolve));
|
||||
|
||||
// Register URL scheme if needed
|
||||
checkAndRegisterUrlScheme(log);
|
||||
|
||||
// Check if test data files exist first
|
||||
const requiredFiles = [
|
||||
'.generated/test-env.json',
|
||||
'.generated/claim_details.json',
|
||||
'.generated/contacts.json'
|
||||
];
|
||||
|
||||
for (const file of requiredFiles) {
|
||||
if (!existsSync(file)) {
|
||||
log(`⚠️ Required file ${file} does not exist`);
|
||||
log('⚠️ Skipping deeplink tests');
|
||||
rl.close();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if our app is actually running in the simulator
|
||||
log('🔍 Checking if app is currently running in simulator...');
|
||||
// Validate test data before proceeding
|
||||
let testEnv, contacts;
|
||||
try {
|
||||
const runningApps = execSync('xcrun simctl listapps booted').toString();
|
||||
const appIdentifier = getAppIdentifier();
|
||||
|
||||
if (!runningApps.includes(appIdentifier)) {
|
||||
log('⚠️ The app does not appear to be running in the simulator.');
|
||||
const shouldLaunch = await question('Would you like to launch the app now? (y/n): ');
|
||||
|
||||
if (shouldLaunch.toLowerCase() === 'y' || shouldLaunch.toLowerCase() === 'yes') {
|
||||
// Try launching the app again
|
||||
log('🚀 Launching app in simulator...');
|
||||
const simulatorInfo = JSON.parse(execSync('xcrun simctl list -j devices booted').toString());
|
||||
const booted = Object.values(simulatorInfo.devices)
|
||||
.flat()
|
||||
.find(device => device.state === 'Booted');
|
||||
|
||||
if (booted) {
|
||||
execSync(`npx cap run ios --target="${booted.udid}"`, { stdio: 'inherit' });
|
||||
log('✅ App launched');
|
||||
} else {
|
||||
log('⚠️ No booted simulator found');
|
||||
}
|
||||
} else {
|
||||
log('⚠️ Deeplink tests require the app to be running');
|
||||
log('⚠️ Please launch the app manually and restart the tests');
|
||||
rl.close();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
log('✅ App is running in simulator');
|
||||
}
|
||||
({ testEnv, contacts } = validateTestData(log));
|
||||
} catch (error) {
|
||||
log(`⚠️ Unable to check if app is running: ${error.message}`);
|
||||
log('⚠️ Proceeding with deeplink tests, but they may fail if app is not running');
|
||||
log('❌ Cannot proceed with tests due to invalid test data');
|
||||
log(`Error: ${error.message}`);
|
||||
log('Please ensure test data is properly generated before running tests');
|
||||
process.exit(1); // Exit with error code
|
||||
}
|
||||
|
||||
// Now we can safely create the deeplink tests knowing we have valid data
|
||||
const deeplinkTests = [
|
||||
{
|
||||
url: `timesafari://claim/${testEnv.CLAIM_ID}`,
|
||||
description: 'Claim view'
|
||||
},
|
||||
{
|
||||
url: `timesafari://claim-cert/${testEnv.CERT_ID || testEnv.CLAIM_ID}`,
|
||||
description: 'Claim certificate view'
|
||||
},
|
||||
{
|
||||
url: `timesafari://claim-add-raw/${testEnv.RAW_CLAIM_ID || testEnv.CLAIM_ID}`,
|
||||
description: 'Raw claim addition'
|
||||
},
|
||||
{
|
||||
url: 'timesafari://did/test',
|
||||
description: 'DID view with test identifier'
|
||||
},
|
||||
{
|
||||
url: `timesafari://did/${testEnv.CONTACT1_DID}`,
|
||||
description: 'DID view with contact DID'
|
||||
},
|
||||
{
|
||||
url: (() => {
|
||||
if (!testEnv?.CONTACT1_DID) {
|
||||
throw new Error('Cannot construct contact-edit URL: CONTACT1_DID is missing');
|
||||
}
|
||||
const url = `timesafari://contact-edit/${testEnv.CONTACT1_DID}`;
|
||||
log('Created contact-edit URL:', url);
|
||||
return url;
|
||||
})(),
|
||||
description: 'Contact editing'
|
||||
},
|
||||
{
|
||||
url: `timesafari://contacts/import?contacts=${encodeURIComponent(JSON.stringify(contacts))}`,
|
||||
description: 'Contacts import'
|
||||
}
|
||||
];
|
||||
|
||||
// Log the final test configuration
|
||||
log('\n5. Final Test Configuration:');
|
||||
deeplinkTests.forEach((test, i) => {
|
||||
log(`\nTest ${i + 1}:`);
|
||||
log(`Description: ${test.description}`);
|
||||
log(`URL: ${test.url}`);
|
||||
});
|
||||
|
||||
// Show instructions for iOS security dialogs
|
||||
log('\n📱 IMPORTANT: iOS Security Dialog Instructions:');
|
||||
log('1. Each deeplink test will trigger a security confirmation dialog');
|
||||
@@ -627,110 +682,41 @@ const runDeeplinkTests = async (log) => {
|
||||
await question('Press Enter when the app is visible and in the foreground...');
|
||||
|
||||
try {
|
||||
// Load test data
|
||||
log('📂 Loading test data from .generated directory');
|
||||
let testEnv, claimDetails, contacts;
|
||||
|
||||
try {
|
||||
const testEnvContent = readFileSync('.generated/test-env.json', 'utf8');
|
||||
testEnv = JSON.parse(testEnvContent);
|
||||
log('✅ Loaded test-env.json');
|
||||
} catch (error) {
|
||||
log(`⚠️ Failed to load test-env.json: ${error.message}`);
|
||||
rl.close();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const claimDetailsContent = readFileSync('.generated/claim_details.json', 'utf8');
|
||||
claimDetails = JSON.parse(claimDetailsContent);
|
||||
log('✅ Loaded claim_details.json');
|
||||
} catch (error) {
|
||||
log(`⚠️ Failed to load claim_details.json: ${error.message}`);
|
||||
rl.close();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const contactsContent = readFileSync('.generated/contacts.json', 'utf8');
|
||||
contacts = JSON.parse(contactsContent);
|
||||
log('✅ Loaded contacts.json');
|
||||
} catch (error) {
|
||||
log(`⚠️ Failed to load contacts.json: ${error.message}`);
|
||||
rl.close();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the app URL scheme is registered in the simulator
|
||||
log('🔍 Checking if URL scheme is registered in simulator...');
|
||||
try {
|
||||
// Attempt to open a simple URL with the scheme
|
||||
log('⚠️ A security dialog will appear - Click "Open" to continue');
|
||||
execSync(`xcrun simctl openurl booted "timesafari://test"`, { stdio: 'pipe' });
|
||||
log('✅ URL scheme is registered and working');
|
||||
} catch (error) {
|
||||
const errorMessage = error.message || '';
|
||||
|
||||
// Check for the specific error code that indicates an unregistered URL scheme
|
||||
if (errorMessage.includes('OSStatus error -10814') || errorMessage.includes('NSOSStatusErrorDomain, code=-10814')) {
|
||||
log('⚠️ URL scheme "timesafari://" is not registered in the app or app is not running');
|
||||
log('⚠️ The scheme was added to Info.plist but the app may need to be rebuilt');
|
||||
log('⚠️ Trying to continue with tests, but they may fail');
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for user confirmation before proceeding
|
||||
await question('Press Enter to continue with the tests...');
|
||||
|
||||
// Test URLs
|
||||
const deeplinkTests = [
|
||||
{
|
||||
url: `timesafari://claim/${claimDetails.claim_id}`,
|
||||
description: 'Claim view'
|
||||
},
|
||||
{
|
||||
url: `timesafari://claim-cert/${claimDetails.claim_id}`,
|
||||
description: 'Claim certificate view'
|
||||
},
|
||||
{
|
||||
url: `timesafari://claim-add-raw/${claimDetails.claim_id}`,
|
||||
description: 'Raw claim addition'
|
||||
},
|
||||
{
|
||||
url: 'timesafari://did/test',
|
||||
description: 'DID view with test identifier'
|
||||
},
|
||||
{
|
||||
url: `timesafari://did/${testEnv.CONTACT1_DID}`,
|
||||
description: 'DID view with contact DID'
|
||||
},
|
||||
{
|
||||
url: `timesafari://contact-edit/${testEnv.CONTACT1_DID}`,
|
||||
description: 'Contact editing'
|
||||
},
|
||||
{
|
||||
url: `timesafari://contacts/import?contacts=${encodeURIComponent(JSON.stringify(contacts))}`,
|
||||
description: 'Contacts import'
|
||||
}
|
||||
];
|
||||
|
||||
// Execute each test
|
||||
let testsCompleted = 0;
|
||||
let testsSkipped = 0;
|
||||
|
||||
for (const test of deeplinkTests) {
|
||||
// Show upcoming test info before execution
|
||||
log('\n📱 NEXT TEST:');
|
||||
log('------------------------');
|
||||
log(`Description: ${test.description}`);
|
||||
log(`URL to test: ${test.url}`);
|
||||
log('------------------------');
|
||||
|
||||
// Clear prompt for user action
|
||||
await question('\n⏎ Press Enter to execute this test (or Ctrl+C to quit)...');
|
||||
|
||||
try {
|
||||
log(`\n🔗 Testing deeplink: ${test.description}`);
|
||||
log(`URL: ${test.url}`);
|
||||
log('🚀 Executing deeplink test...');
|
||||
log('⚠️ iOS SECURITY DIALOG WILL APPEAR - Click "Open" to continue');
|
||||
|
||||
execSync(`xcrun simctl openurl booted "${test.url}"`, { stdio: 'pipe' });
|
||||
log(`✅ Successfully executed: ${test.description}`);
|
||||
testsCompleted++;
|
||||
|
||||
// Wait for user to press Enter before continuing to next test
|
||||
// Show progress
|
||||
log(`\n📊 Progress: ${testsCompleted}/${deeplinkTests.length} tests completed`);
|
||||
|
||||
// If there are more tests, show the next one
|
||||
if (testsCompleted < deeplinkTests.length) {
|
||||
await question('Press Enter to continue to the next test...');
|
||||
const nextTest = deeplinkTests[testsCompleted];
|
||||
log('\n⏭️ NEXT UP:');
|
||||
log('------------------------');
|
||||
log(`Next test will be: ${nextTest.description}`);
|
||||
log(`URL: ${nextTest.url}`);
|
||||
log('------------------------');
|
||||
await question('\n⏎ Press Enter when ready for the next test...');
|
||||
}
|
||||
} catch (deeplinkError) {
|
||||
const errorMessage = deeplinkError.message || '';
|
||||
@@ -745,14 +731,22 @@ const runDeeplinkTests = async (log) => {
|
||||
}
|
||||
log('⚠️ Continuing with next test...');
|
||||
|
||||
// Wait for user to press Enter before continuing to next test
|
||||
// Show next test info after error handling
|
||||
if (testsCompleted + testsSkipped < deeplinkTests.length) {
|
||||
await question('Press Enter to continue to the next test...');
|
||||
const nextTest = deeplinkTests[testsCompleted + testsSkipped];
|
||||
log('\n⏭️ NEXT UP:');
|
||||
log('------------------------');
|
||||
log(`Next test will be: ${nextTest.description}`);
|
||||
log(`URL: ${nextTest.url}`);
|
||||
log('------------------------');
|
||||
await question('\n⏎ Press Enter when ready for the next test...');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log(`✅ Deeplink tests completed: ${testsCompleted} successful, ${testsSkipped} skipped`);
|
||||
log('\n🎉 All deeplink tests completed!');
|
||||
log(`✅ Successful: ${testsCompleted}`);
|
||||
log(`⚠️ Skipped: ${testsSkipped}`);
|
||||
|
||||
if (testsSkipped > 0) {
|
||||
log('\n📝 Note about skipped tests:');
|
||||
@@ -762,15 +756,9 @@ const runDeeplinkTests = async (log) => {
|
||||
log('4. iOS security dialogs must be manually approved for each deeplink test');
|
||||
log('5. If these conditions are met and tests still fail, check URL handling in the app code');
|
||||
}
|
||||
|
||||
// Close readline interface
|
||||
rl.close();
|
||||
} catch (error) {
|
||||
log(`❌ Deeplink tests setup failed: ${error.message}`);
|
||||
log('⚠️ Deeplink tests might be unavailable or test data is missing');
|
||||
// Close readline interface
|
||||
rl.close();
|
||||
// Don't rethrow the error to prevent halting the process
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ import {
|
||||
deepLinkSchemas,
|
||||
baseUrlSchema,
|
||||
routeSchema,
|
||||
DeepLinkRoute,
|
||||
} from "../types/deepLinks";
|
||||
import { logConsoleAndDb } from "../db";
|
||||
import type { DeepLinkError } from "../interfaces/deepLinks";
|
||||
@@ -120,12 +121,12 @@ export class DeepLinkHandler {
|
||||
"invite-one-accept": "invite-one-accept",
|
||||
"contact-import": "contact-import",
|
||||
"confirm-gift": "confirm-gift",
|
||||
claim: "claim",
|
||||
"claim": "claim",
|
||||
"claim-cert": "claim-cert",
|
||||
"claim-add-raw": "claim-add-raw",
|
||||
"contact-edit": "contact-edit",
|
||||
contacts: "contacts",
|
||||
did: "did",
|
||||
"contacts": "contacts",
|
||||
"did": "did",
|
||||
};
|
||||
|
||||
// First try to validate the route path
|
||||
|
||||
@@ -1,10 +1,24 @@
|
||||
<template>
|
||||
<div class="deep-link-error">
|
||||
<div class="safe-area-spacer"></div>
|
||||
<h1>Invalid Deep Link</h1>
|
||||
<div class="error-details">
|
||||
<p>{{ errorMessage }}</p>
|
||||
<div class="error-message">
|
||||
<h3>Error Details</h3>
|
||||
<p>{{ errorMessage }}</p>
|
||||
<div v-if="errorCode" class="error-code">
|
||||
Error Code: <span>{{ errorCode }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="originalPath" class="original-link">
|
||||
<strong>Link attempted:</strong> timesafari://{{ originalPath }}
|
||||
<h3>Attempted Link</h3>
|
||||
<code>timesafari://{{ formattedPath }}</code>
|
||||
<div class="debug-info">
|
||||
<h4>Parameters:</h4>
|
||||
<pre>{{ JSON.stringify(route.params, null, 2) }}</pre>
|
||||
<h4>Query:</h4>
|
||||
<pre>{{ JSON.stringify(route.query, null, 2) }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="actions">
|
||||
@@ -17,7 +31,7 @@
|
||||
<h2>Supported Deep Links</h2>
|
||||
<ul>
|
||||
<li v-for="route in validRoutes" :key="route">
|
||||
timesafari://{{ route }}/:id
|
||||
<code>timesafari://{{ route }}/:id</code>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -45,6 +59,19 @@ const errorMessage = computed(
|
||||
const originalPath = computed(() => route.query.originalPath as string);
|
||||
const validRoutes = VALID_DEEP_LINK_ROUTES;
|
||||
|
||||
// Format the path and include any parameters
|
||||
const formattedPath = computed(() => {
|
||||
if (!originalPath.value) return '';
|
||||
let path = originalPath.value.replace(/^\/+/, '');
|
||||
|
||||
// Log for debugging
|
||||
console.log('Original Path:', originalPath.value);
|
||||
console.log('Route Params:', route.params);
|
||||
console.log('Route Query:', route.query);
|
||||
|
||||
return path;
|
||||
});
|
||||
|
||||
// Navigation methods
|
||||
const goHome = () => router.replace({ name: "home" });
|
||||
const reportIssue = () => {
|
||||
@@ -60,8 +87,144 @@ const reportIssue = () => {
|
||||
// Log the error for analytics
|
||||
onMounted(() => {
|
||||
logConsoleAndDb(
|
||||
`[DeepLink] Error page displayed for path: ${originalPath.value}, code: ${errorCode.value}`,
|
||||
`[DeepLink] Error page displayed for path: ${originalPath.value}, code: ${errorCode.value}, params: ${JSON.stringify(route.params)}`,
|
||||
true,
|
||||
);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.deep-link-error {
|
||||
padding-top: 60px;
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.safe-area-spacer {
|
||||
height: env(safe-area-inset-top);
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #ff4444;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
h2, h3 {
|
||||
color: #333;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.error-details {
|
||||
background-color: #f8f8f8;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.error-message p {
|
||||
color: #666;
|
||||
line-height: 1.5;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.error-code {
|
||||
font-family: monospace;
|
||||
color: #666;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.error-code span {
|
||||
background-color: #eee;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.original-link {
|
||||
padding: 12px;
|
||||
background-color: #fff;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.original-link code {
|
||||
color: #0066cc;
|
||||
font-family: monospace;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.actions {
|
||||
margin: 24px 0;
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.actions button {
|
||||
padding: 10px 20px;
|
||||
border-radius: 6px;
|
||||
border: none;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.primary-button {
|
||||
background-color: #007aff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.secondary-button {
|
||||
background-color: #f2f2f2;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.supported-links {
|
||||
margin-top: 32px;
|
||||
}
|
||||
|
||||
.supported-links ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.supported-links li {
|
||||
padding: 8px 12px;
|
||||
background-color: #f8f8f8;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.supported-links code {
|
||||
font-family: monospace;
|
||||
color: #0066cc;
|
||||
}
|
||||
|
||||
.debug-info {
|
||||
margin-top: 16px;
|
||||
padding: 12px;
|
||||
background-color: #f0f0f0;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.debug-info h4 {
|
||||
margin: 8px 0;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.debug-info pre {
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
color: #333;
|
||||
background-color: #fff;
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
margin: 4px 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user