-
Notifications
You must be signed in to change notification settings - Fork 4
Chrome extension #12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
BaljinderHothi
wants to merge
9
commits into
main
Choose a base branch
from
Chrome-Extension
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Chrome extension #12
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
dc5133d
adding the initial chrome extension information
BaljinderHothi 3fab458
Adding bassic endpoints for the extension, will add more for the ML a…
BaljinderHothi 1138b7a
adding more logic and more endpoints for the ML model and reminders f…
BaljinderHothi 7c08ba5
adding some of the contentScripts, more to come next push
BaljinderHothi d89c3c6
added basic html for the review popup, the stars do not work they sho…
BaljinderHothi cfc074e
added review logic and more, additioanlly expanded contentscript.js t…
BaljinderHothi e67ffe7
adding additional files that i forgot to push yesterday
BaljinderHothi 59340a9
adding the changes to the manifest.json
BaljinderHothi 05baeb0
added tests with mockdata, am not pushing the workflows and the jest …
BaljinderHothi File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
103 changes: 103 additions & 0 deletions
103
Chrome Extension/__tests__/purchase-detection.integration.test.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,103 @@ | ||
| // Integration tests for purchase detection and saving | ||
| describe('Purchase Detection and Storage Integration', () => { | ||
| beforeEach(() => { | ||
|
|
||
| document.body.innerHTML = ` | ||
| <div class="product-title">Test Product</div> | ||
| <div class="price">$29.99</div> | ||
| `; | ||
|
|
||
|
|
||
| chrome.runtime.sendMessage.mockImplementation((message, callback) => { | ||
| if (message.type === 'PURCHASE_DETECTED') { | ||
|
|
||
| savePurchaseData(message.data); | ||
| } | ||
| if (callback) callback({ success: true }); | ||
| return true; | ||
| }); | ||
|
|
||
|
|
||
| chrome.storage.local.get.mockImplementation((keys, callback) => { | ||
| if (keys.includes('userToken')) { | ||
| callback({ userToken: 'test-token-123' }); | ||
| } | ||
| }); | ||
|
|
||
|
|
||
| global.fetch = jest.fn(() => | ||
| Promise.resolve({ | ||
| json: () => Promise.resolve({ success: true, id: 'saved-purchase-123' }) | ||
| }) | ||
| ); | ||
| }); | ||
|
|
||
| test('should detect purchase and send data to background script', () => { | ||
|
|
||
| Object.defineProperty(window, 'location', { | ||
| value: { href: 'https://example.com/confirmation' } | ||
| }); | ||
|
|
||
|
|
||
| detectPurchase(); | ||
|
|
||
|
|
||
| expect(chrome.runtime.sendMessage).toHaveBeenCalledWith({ | ||
| type: 'PURCHASE_DETECTED', | ||
| data: expect.objectContaining({ | ||
| item: 'Test Product', | ||
| price: '$29.99', | ||
| url: 'https://example.com/confirmation' | ||
| }) | ||
| }); | ||
| }); | ||
|
|
||
| test('should store purchase data when user is logged in', async () => { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What makes this an integration tests? |
||
| const purchaseData = { | ||
| item: 'Test Product', | ||
| price: '$29.99', | ||
| url: 'https://example.com/product', | ||
| timestamp: new Date().toISOString() | ||
| }; | ||
|
|
||
|
|
||
| await savePurchaseData(purchaseData); | ||
|
|
||
|
|
||
| expect(chrome.storage.local.get).toHaveBeenCalledWith(['userToken'], expect.any(Function)); | ||
|
|
||
|
|
||
| expect(fetch).toHaveBeenCalledWith( | ||
| expect.any(String), | ||
| expect.objectContaining({ | ||
| method: 'POST', | ||
| headers: expect.objectContaining({ | ||
| 'Authorization': 'Bearer test-token-123' | ||
| }), | ||
| body: expect.stringContaining('Test Product') | ||
| }) | ||
| ); | ||
| }); | ||
|
|
||
| test('should prompt for login when token is missing', async () => { | ||
|
|
||
| chrome.storage.local.get.mockImplementationOnce((keys, callback) => { | ||
| callback({}); | ||
| }); | ||
|
|
||
|
|
||
| const promptUserLogin = jest.fn(); | ||
|
|
||
| const purchaseData = { | ||
| item: 'Test Product', | ||
| price: '$29.99' | ||
| }; | ||
|
|
||
| await savePurchaseData(purchaseData); | ||
|
|
||
| // Verify login was prompted | ||
| expect(promptUserLogin).toHaveBeenCalled(); | ||
| // Verify API was not called | ||
| expect(fetch).not.toHaveBeenCalled(); | ||
| }); | ||
| }); | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| // Unit tests for review functionality in background.js | ||
| describe('Review Management Unit Tests', () => { | ||
| beforeEach(() => { | ||
| chrome.storage.local.get.mockImplementation((keys, callback) => { | ||
| callback({ pendingReviews: [ | ||
| { | ||
| purchaseId: 'test123', | ||
| product: 'Test Product', | ||
| reviewDue: new Date().toISOString(), | ||
| url: 'https://example.com/product' | ||
| } | ||
| ]}); | ||
| }); | ||
|
|
||
| chrome.storage.local.set.mockImplementation((obj, callback) => { | ||
| if (callback) callback(); | ||
| }); | ||
|
|
||
| global.fetch = jest.fn(() => | ||
| Promise.resolve({ | ||
| json: () => Promise.resolve({ success: true }) | ||
| }) | ||
| ); | ||
| }); | ||
|
|
||
| test('should schedule a review reminder with correct timing', () => { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What makes this a unit test? |
||
| const purchaseData = { | ||
| id: 'purchase123', | ||
| item: 'Test Item', | ||
| timestamp: new Date().toISOString(), | ||
| url: 'https://example.com/item' | ||
| }; | ||
|
|
||
| scheduleReviewReminder(purchaseData); | ||
|
|
||
| expect(chrome.storage.local.get).toHaveBeenCalledWith(['pendingReviews'], expect.any(Function)); | ||
| expect(chrome.storage.local.set).toHaveBeenCalled(); | ||
|
|
||
| const setArg = chrome.storage.local.set.mock.calls[0][0]; | ||
| expect(setArg.pendingReviews.length).toBe(2); // Previous + new one | ||
| expect(setArg.pendingReviews[1].purchaseId).toBe('purchase123'); | ||
|
|
||
| const reviewDate = new Date(setArg.pendingReviews[1].reviewDue); | ||
| const purchaseDate = new Date(purchaseData.timestamp); | ||
| const diffDays = Math.round((reviewDate - purchaseDate) / (1000 * 60 * 60 * 24)); | ||
| expect(diffDays).toBe(7); | ||
| }); | ||
|
|
||
| test('should remove review from pending list after submission', () => { | ||
| const purchaseId = 'test123'; | ||
|
|
||
| removeFromPendingReviews(purchaseId); | ||
|
|
||
| expect(chrome.storage.local.get).toHaveBeenCalledWith(['pendingReviews'], expect.any(Function)); | ||
| expect(chrome.storage.local.set).toHaveBeenCalled(); | ||
|
|
||
| const setArg = chrome.storage.local.set.mock.calls[0][0]; | ||
| expect(setArg.pendingReviews.length).toBe(0); | ||
| }); | ||
|
|
||
| test('should handle empty pending reviews gracefully', () => { | ||
| chrome.storage.local.get.mockImplementationOnce((keys, callback) => { | ||
| callback({ pendingReviews: [] }); | ||
| }); | ||
|
|
||
| const purchaseId = 'nonexistent'; | ||
| removeFromPendingReviews(purchaseId); | ||
|
|
||
| expect(chrome.storage.local.set).toHaveBeenCalled(); | ||
| const setArg = chrome.storage.local.set.mock.calls[0][0]; | ||
| expect(setArg.pendingReviews).toEqual([]); | ||
| }); | ||
| }); | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,203 @@ | ||
|
|
||
| chrome.runtime.onInstalled.addListener(() => { | ||
| console.log('Extension installed'); | ||
|
|
||
| checkUserLoggedIn(); | ||
| }); | ||
|
|
||
|
|
||
| function checkUserLoggedIn() { | ||
| chrome.storage.local.get(['userToken'], function(result) { | ||
| /* so here we need to add the endpoint to ensure that the token is still valid, ensuring that the user is | ||
| properly logged in, if they arent we would go to the front end page and ensure that they are logged in | ||
|
|
||
| this would be done in tandem to @anas and @nirath as we need to check the database and the front end signUp page | ||
| */ | ||
| }); | ||
| } | ||
|
|
||
|
|
||
| chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { | ||
| if (message.type === 'PURCHASE_DETECTED') { | ||
| /* Here we would have the endpoint to take the purchase data and send it to the back end | ||
| we have to decide how we store the purchases properly still | ||
| ideally we just check the userid and check the date and time and use those to save the purchases | ||
| so that when we are returning purchases and budget, its done through set dates | ||
|
|
||
| ultimately makes it easier for us to filter*/ | ||
|
|
||
| savePurchaseData(message.data); | ||
| } else if (message.type === 'LOGIN_REQUIRED') { | ||
| /* is user is not logged in (line 9), we would then route them to the log in page | ||
| we ensure the log in, once logged in | ||
|
|
||
| if new user do survey | ||
|
|
||
| if not new user we would just jump into working fully */ | ||
|
|
||
| promptUserLogin(); | ||
| } | ||
|
|
||
| return true; //keep the message channel open for async response | ||
| }); | ||
|
|
||
| function savePurchaseData(data) { | ||
| chrome.storage.local.get(['userToken'], function(result) { | ||
| if (!result.userToken) { | ||
| promptUserLogin(); | ||
| return; | ||
| } | ||
| /* endpoint to save the purchase data, header should include auth token, | ||
| data should show all purchase details*/ | ||
|
|
||
| }); | ||
| } | ||
|
|
||
| function promptUserLogin() { | ||
|
|
||
| /* The endpoint to handle auth flow | ||
| this should reroute to the login page that @nirath is working on | ||
|
|
||
| once successful log in is done, we need that to communicate to the extension so | ||
| that it can start working | ||
| it would communicate the auth token for that user*/ | ||
|
|
||
| chrome.tabs.create({ url: 'insert the url here' }); | ||
| } | ||
|
|
||
|
|
||
| chrome.runtime.onMessageExternal.addListener((message, sender, sendResponse) => { | ||
| if (message.type === 'AUTH_TOKEN') { | ||
| /* endpoint to store the auth token | ||
| this should save it to the chrome.storage.local | ||
|
|
||
| this token will be used for all subsequent api calls that are being done*/ | ||
|
|
||
| chrome.storage.local.set({ userToken: message.token }); | ||
| sendResponse({ success: true }); | ||
| } | ||
|
|
||
| return true; | ||
| }); | ||
|
|
||
| function scheduleReviewReminder(purchaseData) { | ||
| /* set up a reminder in the database for specific purchases, | ||
| we then use that reminder to prompt the user to make a review of a purchase lets say after like 10 days from buying it */ | ||
|
|
||
| const purchaseDate = new Date(purchaseData.timestamp); | ||
| const reviewDate = new Date(purchaseDate); | ||
| reviewDate.setDate(reviewDate.getDate() + 7); // One week later | ||
|
|
||
| //review reminders stored locally | ||
| chrome.storage.local.get(['pendingReviews'], function(result) { | ||
| const pendingReviews = result.pendingReviews || []; | ||
| pendingReviews.push({ | ||
| purchaseId: purchaseData.id, | ||
| product: purchaseData.item, | ||
| reviewDue: reviewDate.toISOString(), | ||
| url: purchaseData.url | ||
| }); | ||
|
|
||
| chrome.storage.local.set({ pendingReviews }); | ||
| }); | ||
| } | ||
|
|
||
| function saveProductReview(reviewData) { | ||
| chrome.storage.local.get(['userToken'], function(result) { | ||
| if (!result.userToken) { | ||
| promptUserLogin(); | ||
| return; | ||
| } | ||
|
|
||
| /* create an endpoint to save the review data and send it to the ML model for better fine tuning reviews*/ | ||
|
|
||
| fetch('https://yourapi.com/api/reviews/save', { | ||
| method: 'POST', | ||
| headers: { | ||
| 'Content-Type': 'application/json', | ||
| 'Authorization': `Bearer ${result.userToken}` | ||
| }, | ||
| body: JSON.stringify(reviewData) | ||
| }) | ||
| .then(response => response.json()) | ||
| .then(data => { | ||
| //update the prending review list | ||
| removeFromPendingReviews(reviewData.purchaseId); | ||
|
|
||
| //updates the user with the confirmation | ||
| chrome.notifications.create({ | ||
| type: 'basic', | ||
| iconUrl: 'icons/icon48.png', | ||
| title: 'Review Submitted', | ||
| message: 'Thank you for your feedback! It helps improve recommendations for future purchases!.' | ||
| }); | ||
| }) | ||
| .catch(error => { | ||
| console.error('Error saving review:', error); | ||
| }); | ||
| }); | ||
| } | ||
|
|
||
| //checking for pending due reviews | ||
| function checkPendingReviews() { | ||
| chrome.storage.local.get(['pendingReviews'], function(result) { | ||
| const pendingReviews = result.pendingReviews || []; | ||
| const now = new Date(); | ||
|
|
||
| pendingReviews.forEach(review => { | ||
| const reviewDue = new Date(review.reviewDue); | ||
| if (reviewDue <= now) { | ||
| promptForReview(review); | ||
| } | ||
| }); | ||
| }); | ||
| } | ||
|
|
||
| function promptForReview(reviewItem) { | ||
| /* create the popup for users to leave a review, can honestly look like | ||
|
|
||
| item price | ||
| link 1-5 stars | ||
|
|
||
| Typed message review (if we go that route)*/ | ||
|
|
||
| chrome.windows.create({ | ||
| url: `review.html?purchaseId=${reviewItem.purchaseId}&product=${encodeURIComponent(reviewItem.product)}`, | ||
| type: 'popup', | ||
| width: 400, | ||
| height: 500 | ||
| }); | ||
| } | ||
|
|
||
| //removes review from pending reviews, once a submission has been made | ||
| function removeFromPendingReviews(purchaseId) { | ||
| chrome.storage.local.get(['pendingReviews'], function(result) { | ||
| const pendingReviews = result.pendingReviews || []; | ||
| const updatedReviews = pendingReviews.filter(review => review.purchaseId !== purchaseId); | ||
| chrome.storage.local.set({ pendingReviews: updatedReviews }); | ||
| }); | ||
| } | ||
|
|
||
| chrome.alarms.create('checkReviews', { periodInMinutes: 1440 }); // Once per day | ||
|
|
||
|
|
||
| chrome.alarms.onAlarm.addListener((alarm) => { | ||
| if (alarm.name === 'checkReviews') { | ||
| checkPendingReviews(); | ||
| } | ||
| }); | ||
|
|
||
|
|
||
| chrome.notifications.onButtonClicked.addListener((notificationId, buttonIndex) => { | ||
| if (buttonIndex === 0) { // "See Details" button | ||
| chrome.storage.local.get(['latestRecommendation'], function(result) { | ||
| if (result.latestRecommendation) { | ||
| //open the recommendation details page | ||
| chrome.tabs.create({ | ||
| url: `recommendation.html?data=${encodeURIComponent(JSON.stringify(result.latestRecommendation))}` | ||
| }); | ||
| } | ||
| }); | ||
| } | ||
|
|
||
| }); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to checking OS X artifact into the repository.