diff --git a/package.json b/package.json index 40ee95a74..3e8eca37c 100644 --- a/package.json +++ b/package.json @@ -91,6 +91,7 @@ "eslint-plugin-react": "^7.18.3", "fs-extra": "^8.1.0", "jest": "^25.1.0", + "jest-fetch-mock": "^3.0.3", "jest-when": "^2.7.0", "jsdoc": "^3.6.3", "jsonfile": "^5.0.0", diff --git a/src/background.js b/src/background.js index 923e798e0..5e704253f 100644 --- a/src/background.js +++ b/src/background.js @@ -1472,7 +1472,7 @@ function initializeVersioning() { log('THIS IS AN UPGRADE'); conf.previous_version = globals.EXTENSION_VERSION; const { version_history } = conf; - const earliestVersion = version_history[0].split('.'); + const versions = [...version_history].sort(utils.semverCompare); const prevVersion = PREVIOUS_EXTENSION_VERSION.split('.'); const currentVersion = globals.EXTENSION_VERSION.split('.'); @@ -1490,7 +1490,7 @@ function initializeVersioning() { } // Check if the earliest version is < 8.4.2 - if (earliestVersion[0] <= 8 && earliestVersion[1] <= 4 && earliestVersion[2] < 2) { + if (versions.length && utils.semverCompare(versions[0], '8.4.2') === -1) { globals.REQUIRE_LEGACY_OPT_IN = true; } diff --git a/src/utils/utils.js b/src/utils/utils.js index e4f261523..faa6bae12 100644 --- a/src/utils/utils.js +++ b/src/utils/utils.js @@ -617,3 +617,23 @@ export function injectNotifications(tab_id, importExport = false) { export function isCliqzOffer(offer) { return (offer && offer.origin === 'cliqz' && offer.type === 'offers' && offer.data); } + +/** + * Compare semantic version strings + * @param {string} a semantic version (x.x.x) + * @param {string} b semantic version (x.x.x) + * @return {int} (a > b) = 1, (a < b) = -1, (a == b) = 0 + */ +export function semverCompare(a, b) { + const pa = a.split('.'); + const pb = b.split('.'); + for (let i = 0; i < 3; i++) { + const na = Number(pa[i]); + const nb = Number(pb[i]); + if (na > nb) return 1; + if (nb > na) return -1; + if (!Number.isNaN(na) && Number.isNaN(nb)) return 1; + if (Number.isNaN(na) && !Number.isNaN(nb)) return -1; + } + return 0; +} diff --git a/test/setup.js b/test/setup.js index 6ce671e6f..49c2ef720 100644 --- a/test/setup.js +++ b/test/setup.js @@ -32,7 +32,7 @@ chrome.runtime.getManifest.returns({ debug: true }); -// Create Mock for for Cliqz modules +// Create Mock for Cliqz modules jest.mock('../src/classes/Cliqz', () => ({ modules: { adblocker: { diff --git a/test/src/utils.test.js b/test/src/utils.test.js index cc369a6ca..1970a7668 100644 --- a/test/src/utils.test.js +++ b/test/src/utils.test.js @@ -11,70 +11,119 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0 */ -import sinon from 'sinon'; -import 'whatwg-fetch'; -import { getJson, defineLazyProperty } from '../../src/utils/utils'; +import { enableFetchMocks } from 'jest-fetch-mock' +import { getJson, defineLazyProperty, semverCompare } from '../../src/utils/utils'; -describe('tests for getJson()', () => { - // Setup getJson() by initializing fetch() - beforeEach(() => { - sinon.stub(global, 'fetch'); - }); - - // Reset fetch() - afterEach(() => { - global.fetch.restore(); - }); +// Mock fetch calls +enableFetchMocks() - // Helper function to fake XHR requests - function setFetchStubResponse (responseCode, responseData) { - const res = new global.Response(responseData, { +describe('tests for getJson()', () => { + // Helper function to fake Fetch response + function mockFetchResponse (responseCode, responseData) { + fetch.mockReturnValue(Promise.resolve(new Response(responseData, { status: responseCode, headers: { 'Content-type': 'application/json' } - }); - global.fetch.returns(Promise.resolve(res)); + }))); } // Tests for getJson() test('returns a 200 response', () => { - setFetchStubResponse(200, JSON.stringify({ hello: 'world' })); - return expect(getJson('testurl')).resolves.toEqual({ hello: 'world' }); + mockFetchResponse(200, JSON.stringify({ hello: 'world' })); + expect(getJson('https://www.ghostery.com/')).resolves.toEqual({ hello: 'world' }); }); test('returns a 404 response', () => { - setFetchStubResponse(404, 'Not Found'); - return expect(getJson('testurl')).rejects.toThrow(/404/); + mockFetchResponse(404, 'Not Found'); + expect(getJson('https://www.ghostery.com/')).rejects.toThrow(/404/); }); }); describe('tests for defineLazyProperty()', () => { - let o = {}, - expensiveComputation = sinon.spy(() => 'hello'), - shouldNeverGetCalled = sinon.spy(() => 'hello again'); + const testObject = { + expensiveComputation() { + return 'hello'; + }, + shouldNeverGetCalled() { + return 'hello again'; + } + }; + const expensiveSpy = jest.spyOn(testObject, 'expensiveComputation'); + const neverCalledSpy = jest.spyOn(testObject, 'shouldNeverGetCalled'); - // Tests for defineLazyProperty() test('property function is lazy', () => { - defineLazyProperty(o, 'lazy', expensiveComputation); - return expect(expensiveComputation.callCount).toBe(0); + defineLazyProperty(testObject, 'lazy', testObject.expensiveComputation); + expect(expensiveSpy).not.toHaveBeenCalled(); }); - test('property function is defined', () => expect(o.lazy).toBe('hello')); - test('property function was called', () => expect(expensiveComputation.callCount).toBe(1)); + test('property function is defined', () => expect(testObject.lazy).toBe('hello')); + test('property function was called', () => expect(expensiveSpy).toHaveBeenCalledTimes(1)); - test('property function is still defined', () => expect(o.lazy).toBe('hello')); - test('repeated access do not call property function again', () => expect(expensiveComputation.callCount).toBe(1)); + test('property function is still defined', () => expect(testObject.lazy).toBe('hello')); + test('repeated access do not call property function again', () => expect(expensiveSpy).toHaveBeenCalledTimes(1)); test('reassignment works', () => { - o.lazy = 'something else'; - expect(o.lazy).toBe('something else'); + testObject.lazy = 'something else'; + expect(testObject.lazy).toBe('something else'); }); test('reassignment before access works', () => { - defineLazyProperty(o, 'lazy2', shouldNeverGetCalled); - o.lazy2 = 'nope'; - return expect(o.lazy2).toBe('nope'); + defineLazyProperty(testObject, 'lazy2', testObject.shouldNeverGetCalled); + testObject.lazy2 = 'nope'; + expect(testObject.lazy2).toBe('nope'); + }); + test('property function is still lazy', () => expect(neverCalledSpy).not.toHaveBeenCalled()); +}); + +describe('tests for semverCompare()', () => { + const versions = [ + '1.2.1', + '3.12.6', + '3.2.0', + '1.4.11', + '0.5.7', + '8.1.3', + '2.1.1', + '11.4.1', + '10.7.4', + ]; + + test('Sort version history', () => { + expect(versions.sort(semverCompare)).toEqual([ + '0.5.7', + '1.2.1', + '1.4.11', + '2.1.1', + '3.2.0', + '3.12.6', + '8.1.3', + '10.7.4', + '11.4.1' + ]); + }); + + test('Version comparisons', () => { + // Less Than + expect(semverCompare("7.7.10", "8.7.10")).toBe(-1); + expect(semverCompare("8.6.10", "8.7.10")).toBe(-1); + expect(semverCompare("8.7.1", "8.7.10")).toBe(-1); + expect(semverCompare("7.100.100", "8.7.10")).toBe(-1); + expect(semverCompare("8.3.3", "8.4.2")).toBe(-1); + expect(semverCompare("8.7", "8.7.0")).toBe(-1); + expect(semverCompare("8.7", "8.8.0")).toBe(-1); + + // Greater Than + expect(semverCompare("8.7.10", "7.7.10")).toBe(1); + expect(semverCompare("8.7.10", "8.6.10")).toBe(1); + expect(semverCompare("8.7.10", "8.7.1")).toBe(1); + expect(semverCompare("8.7.10", "7.100.100")).toBe(1); + expect(semverCompare("8.4.2", "8.3.3")).toBe(1); + expect(semverCompare("8.7.0", "8.7")).toBe(1); + expect(semverCompare("8.8.0", "8.7")).toBe(1); + + // Equal To + expect(semverCompare("8.7.10", "8.7.10")).toBe(0); + expect(semverCompare("8.7", "8.7")).toBe(0); }); - test('property function is still lazy', () => expect(shouldNeverGetCalled.callCount).toBe(0)); }); diff --git a/tools/amo/build.sh b/tools/amo/build.sh index 59a250f40..6b2d455e4 100755 --- a/tools/amo/build.sh +++ b/tools/amo/build.sh @@ -29,6 +29,38 @@ if [ ! -d $CLIQZ_SOURCE_DIR ]; then unzip $CLIQZ_SOURCE_ZIP fi +#### REQUIREMENTS #### +# Check for yarn +if ! type yarn > /dev/null; then + echo "Please install yarn: https://yarnpkg.com/lang/en/docs/install/" + exit 1 +fi + +# Check for jq +if ! type jq > /dev/null; then + echo "Please install jq: https://stedolan.github.io/jq/download/" + exit 1 +fi + +# Source nvm.sh +if [[ -f /usr/local/opt/nvm/nvm.sh ]]; then + # Homebrew + source /usr/local/opt/nvm/nvm.sh +else + # Default dir + source ${NVM_DIR}/nvm.sh +fi + +# Check for nvm +if ! command -v nvm | grep -q 'nvm'; then + echo "Please install nvm: https://github.com/nvm-sh/nvm" + exit 1 +fi + +# Set node version +nvm install lts/dubnium +nvm use + #### BROWSER CORE #### cd $CLIQZ_SOURCE_DIR @@ -60,43 +92,12 @@ BUILD_DIR=build ZIP_FILE="$BUILD_DIR/ghostery-extension-v$VERSION.zip" TMP_FILE=$(mktemp) -# Check for yarn -if ! type yarn > /dev/null; then - echo "Please install yarn: https://yarnpkg.com/lang/en/docs/install/" - exit 1 -fi - -# Check for jq -if ! type jq > /dev/null; then - echo "Please install jq: https://stedolan.github.io/jq/download/" - exit 1 -fi - -# Source nvm.sh -if [[ -f /usr/local/opt/nvm/nvm.sh ]]; then - # Homebrew - source /usr/local/opt/nvm/nvm.sh -else - # Default dir - source ${NVM_DIR}/nvm.sh -fi - -# Check for nvm -if ! command -v nvm | grep -q 'nvm'; then - echo "Please install nvm: https://github.com/nvm-sh/nvm" - exit 1 -fi - # Clean any previous builds rm -rf build # Clean all the exiting node_modules for a more reproducible build rm -rf node_modules -# Set node version -nvm install lts/dubnium -nvm use - # Install local npm packages yarn install --frozen-lockfile @@ -109,10 +110,10 @@ cat ${TMP_FILE} > $VERSION_FILE # copy into manifest.json rm -f ${TMP_FILE} # Download databases -curl "https://cdn.ghostery.com/update/v3/bugs" -o $DB_DIR/bugs.json --compress --fail -curl "https://cdn.ghostery.com/update/click2play" -o $DB_DIR/click2play.json --compress --fail -curl "https://cdn.ghostery.com/update/compatibility" -o $DB_DIR/compatibility.json --compress --fail -curl "https://cdn.ghostery.com/update/surrogates" -o $DB_DIR/surrogates.json --compress --fail +curl "https://cdn.ghostery.com/update/v3/bugs" -o $DB_DIR/bugs.json --compressed --fail +curl "https://cdn.ghostery.com/update/click2play" -o $DB_DIR/click2play.json --compressed --fail +curl "https://cdn.ghostery.com/update/compatibility" -o $DB_DIR/compatibility.json --compressed --fail +curl "https://cdn.ghostery.com/update/surrogates" -o $DB_DIR/surrogates.json --compressed --fail # Zip final build files echo "Zipping to $(pwd)/$BUILD_DIR/" diff --git a/yarn.lock b/yarn.lock index 4767c0d59..186187d84 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2166,6 +2166,14 @@ cross-env@^7.0.0: dependencies: cross-spawn "^7.0.1" +cross-fetch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.0.4.tgz#7bef7020207e684a7638ef5f2f698e24d9eb283c" + integrity sha512-MSHgpjQqgbT/94D4CyADeNoYh52zMkCX4pcJvPP5WqPsLFMKjr2TCMg381ox5qI0ii2dPwaLx/00477knXqXVw== + dependencies: + node-fetch "2.6.0" + whatwg-fetch "3.0.0" + cross-spawn@6.0.5, cross-spawn@^6.0.0, cross-spawn@^6.0.5: version "6.0.5" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" @@ -4799,6 +4807,14 @@ jest-environment-node@^25.1.0: jest-mock "^25.1.0" jest-util "^25.1.0" +jest-fetch-mock@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/jest-fetch-mock/-/jest-fetch-mock-3.0.3.tgz#31749c456ae27b8919d69824f1c2bd85fe0a1f3b" + integrity sha512-Ux1nWprtLrdrH4XwE7O7InRY6psIi3GOsqNESJgMJ+M5cv4A8Lh7SN9d2V2kKRZ8ebAfcd1LNyZguAOb6JiDqw== + dependencies: + cross-fetch "^3.0.4" + promise-polyfill "^8.1.3" + jest-get-type@^24.9.0: version "24.9.0" resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-24.9.0.tgz#1684a0c8a50f2e4901b6644ae861f579eed2ef0e" @@ -5946,6 +5962,11 @@ nise@^3.0.1: lolex "^5.0.1" path-to-regexp "^1.7.0" +node-fetch@2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd" + integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA== + node-fetch@^1.0.1, node-fetch@^1.7.1: version "1.7.3" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef" @@ -6768,6 +6789,11 @@ promise-inflight@^1.0.1: resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM= +promise-polyfill@^8.1.3: + version "8.1.3" + resolved "https://registry.yarnpkg.com/promise-polyfill/-/promise-polyfill-8.1.3.tgz#8c99b3cf53f3a91c68226ffde7bde81d7f904116" + integrity sha512-MG5r82wBzh7pSKDRa9y+vllNHz3e3d4CNj1PQE4BQYxLme0gKYYBm9YENq+UkEikyZ0XbiGWxYlVw3Rl9O/U8g== + promise@^7.1.1: version "7.3.1" resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" @@ -9103,7 +9129,7 @@ whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.5: dependencies: iconv-lite "0.4.24" -whatwg-fetch@>=0.10.0, whatwg-fetch@^3.0.0: +whatwg-fetch@3.0.0, whatwg-fetch@>=0.10.0, whatwg-fetch@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz#fc804e458cc460009b1a2b966bc8817d2578aefb" integrity sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q==