From 3cdd9a560ee8de0bd82caf2b54118e5c173c8d9a Mon Sep 17 00:00:00 2001 From: Damien Pitard Date: Fri, 5 Oct 2018 11:04:29 +0200 Subject: [PATCH 1/4] Upgraded to intl-tel-input:14.0.0 --- bower.json | 2 +- dist/ng-intl-tel-input.js | 16 +++++++++------- dist/ng-intl-tel-input.min.js | 2 +- karma.conf.js | 1 + ng-intl-tel-input.directive.js | 10 +++++----- ng-intl-tel-input.directive.spec.js | 15 +++++++++------ ng-intl-tel-input.provider.js | 2 +- ng-intl-tel-input.provider.spec.js | 22 +++++++++++----------- package.json | 2 +- test-helpers.js | 9 +++++++++ 10 files changed, 48 insertions(+), 33 deletions(-) create mode 100644 test-helpers.js diff --git a/bower.json b/bower.json index 61ed12d..91d1213 100644 --- a/bower.json +++ b/bower.json @@ -26,7 +26,7 @@ "tests" ], "dependencies": { - "intl-tel-input": "~12.1.6" + "intl-tel-input": "^14.0.0" }, "devDependencies": { "angularjs": "^1.6.7", diff --git a/dist/ng-intl-tel-input.js b/dist/ng-intl-tel-input.js index 715c5bb..e3c9dcc 100644 --- a/dist/ng-intl-tel-input.js +++ b/dist/ng-intl-tel-input.js @@ -1,4 +1,5 @@ -angular.module('ngIntlTelInput', []);angular.module('ngIntlTelInput') +angular.module('ngIntlTelInput', []); +angular.module('ngIntlTelInput') .provider('ngIntlTelInput', function () { var me = this; var props = {}; @@ -18,12 +19,13 @@ angular.module('ngIntlTelInput', []);angular.module('ngIntlTelInput') if (!window.intlTelInputUtils) { $log.warn('intlTelInputUtils is not defined. Formatting and validation will not work.'); } - elm.intlTelInput(props); + return window.intlTelInput(elm[0], props); } }, }); }]; }); + angular.module('ngIntlTelInput') .directive('ngIntlTelInput', ['ngIntlTelInput', '$log', '$window', '$parse', function (ngIntlTelInput, $log, $window, $parse) { @@ -41,12 +43,12 @@ angular.module('ngIntlTelInput') ngIntlTelInput.set({initialCountry: attr.initialCountry}); } // Initialize. - ngIntlTelInput.init(elm); + var iti = ngIntlTelInput.init(elm); // Set Selected Country Data. function setSelectedCountryData(model) { var getter = $parse(model); var setter = getter.assign; - setter(scope, elm.intlTelInput('getSelectedCountryData')); + setter(scope, iti.getSelectedCountryData()); } // Handle Country Changes. function handleCountryChange() { @@ -66,14 +68,14 @@ angular.module('ngIntlTelInput') ctrl.$validators.ngIntlTelInput = function (value) { // if phone number is deleted / empty do not run phone number validation if (value || elm[0].value.length > 0) { - return elm.intlTelInput('isValidNumber'); + return iti.isValidNumber(); } else { return true; } }; // Set model value to valid, formatted version. ctrl.$parsers.push(function (value) { - return elm.intlTelInput('getNumber'); + return iti.getNumber(); }); // Set input value to model value and trigger evaluation. ctrl.$formatters.push(function (value) { @@ -81,7 +83,7 @@ angular.module('ngIntlTelInput') if(value.charAt(0) !== '+') { value = '+' + value; } - elm.intlTelInput('setNumber', value); + iti.setNumber(value); } return value; }); diff --git a/dist/ng-intl-tel-input.min.js b/dist/ng-intl-tel-input.min.js index a422804..2615e17 100644 --- a/dist/ng-intl-tel-input.min.js +++ b/dist/ng-intl-tel-input.min.js @@ -1 +1 @@ -angular.module("ngIntlTelInput",[]),angular.module("ngIntlTelInput").provider("ngIntlTelInput",function(){var a=this,b={},c=function(a){if("object"==typeof a)for(var c in a)b[c]=a[c]};a.set=c,a.$get=["$log",function(c){return Object.create(a,{init:{value:function(a){window.intlTelInputUtils||c.warn("intlTelInputUtils is not defined. Formatting and validation will not work."),a.intlTelInput(b)}}})}]}),angular.module("ngIntlTelInput").directive("ngIntlTelInput",["ngIntlTelInput","$log","$window","$parse",function(a,b,c,d){return{restrict:"A",require:"ngModel",link:function(e,f,g,h){function i(a){var b=d(a),c=b.assign;c(e,f.intlTelInput("getSelectedCountryData"))}function j(){i(g.selectedCountry)}function k(){angular.element(c).off("countrychange",j)}return g.type&&"text"!==g.type&&"tel"!==g.type||"INPUT"!==f[0].tagName?void b.warn("ng-intl-tel-input can only be applied to a *text* or *tel* input"):(g.initialCountry&&a.set({initialCountry:g.initialCountry}),a.init(f),g.selectedCountry&&(i(g.selectedCountry),angular.element(c).on("countrychange",j),e.$on("$destroy",k)),h.$validators.ngIntlTelInput=function(a){return a||f[0].value.length>0?f.intlTelInput("isValidNumber"):!0},h.$parsers.push(function(a){return f.intlTelInput("getNumber")}),void h.$formatters.push(function(a){return a&&("+"!==a.charAt(0)&&(a="+"+a),f.intlTelInput("setNumber",a)),a}))}}}]); \ No newline at end of file +angular.module("ngIntlTelInput",[]),angular.module("ngIntlTelInput").provider("ngIntlTelInput",function(){var a=this,b={},c=function(a){if("object"==typeof a)for(var c in a)b[c]=a[c]};a.set=c,a.$get=["$log",function(c){return Object.create(a,{init:{value:function(a){return window.intlTelInputUtils||c.warn("intlTelInputUtils is not defined. Formatting and validation will not work."),window.intlTelInput(a[0],b)}}})}]}),angular.module("ngIntlTelInput").directive("ngIntlTelInput",["ngIntlTelInput","$log","$window","$parse",function(a,b,c,d){return{restrict:"A",require:"ngModel",link:function(e,f,g,h){function i(a){var b=d(a),c=b.assign;c(e,l.getSelectedCountryData())}function j(){i(g.selectedCountry)}function k(){angular.element(c).off("countrychange",j)}if(g.type&&"text"!==g.type&&"tel"!==g.type||"INPUT"!==f[0].tagName)return void b.warn("ng-intl-tel-input can only be applied to a *text* or *tel* input");g.initialCountry&&a.set({initialCountry:g.initialCountry});var l=a.init(f);g.selectedCountry&&(i(g.selectedCountry),angular.element(c).on("countrychange",j),e.$on("$destroy",k)),h.$validators.ngIntlTelInput=function(a){return a||f[0].value.length>0?l.isValidNumber():!0},h.$parsers.push(function(a){return l.getNumber()}),h.$formatters.push(function(a){return a&&("+"!==a.charAt(0)&&(a="+"+a),l.setNumber(a)),a})}}}]); \ No newline at end of file diff --git a/karma.conf.js b/karma.conf.js index 97a555d..2ad9170 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -28,6 +28,7 @@ module.exports = function (config) { 'ng-intl-tel-input.provider.js', 'ng-intl-tel-input.directive.js', 'node_modules/phantomjs-polyfills/polyfills/function-bind-polyfill.js', + 'test-helpers.js', '*.spec.js' ] diff --git a/ng-intl-tel-input.directive.js b/ng-intl-tel-input.directive.js index f09ce2a..f504d5d 100644 --- a/ng-intl-tel-input.directive.js +++ b/ng-intl-tel-input.directive.js @@ -15,12 +15,12 @@ angular.module('ngIntlTelInput') ngIntlTelInput.set({initialCountry: attr.initialCountry}); } // Initialize. - ngIntlTelInput.init(elm); + var iti = ngIntlTelInput.init(elm); // Set Selected Country Data. function setSelectedCountryData(model) { var getter = $parse(model); var setter = getter.assign; - setter(scope, elm.intlTelInput('getSelectedCountryData')); + setter(scope, iti.getSelectedCountryData()); } // Handle Country Changes. function handleCountryChange() { @@ -40,14 +40,14 @@ angular.module('ngIntlTelInput') ctrl.$validators.ngIntlTelInput = function (value) { // if phone number is deleted / empty do not run phone number validation if (value || elm[0].value.length > 0) { - return elm.intlTelInput('isValidNumber'); + return iti.isValidNumber(); } else { return true; } }; // Set model value to valid, formatted version. ctrl.$parsers.push(function (value) { - return elm.intlTelInput('getNumber'); + return iti.getNumber(); }); // Set input value to model value and trigger evaluation. ctrl.$formatters.push(function (value) { @@ -55,7 +55,7 @@ angular.module('ngIntlTelInput') if(value.charAt(0) !== '+') { value = '+' + value; } - elm.intlTelInput('setNumber', value); + iti.setNumber(value); } return value; }); diff --git a/ng-intl-tel-input.directive.spec.js b/ng-intl-tel-input.directive.spec.js index 751e448..0e66097 100644 --- a/ng-intl-tel-input.directive.spec.js +++ b/ng-intl-tel-input.directive.spec.js @@ -101,8 +101,9 @@ describe('ng-intl-tel-input', function () { ); $compile(doc)($scope); $scope.$digest(); - element = doc.find('input').eq(0); - expect(element.intlTelInput('getSelectedCountryData').iso2).toEqual('af'); + + var iti = findLastCreatedItiInstance(); + expect(iti.getSelectedCountryData().iso2).toEqual('af'); })); it('should set the country when model value is present', inject(function ($compile) { @@ -114,8 +115,9 @@ describe('ng-intl-tel-input', function () { ); $compile(doc)($scope); $scope.$digest(); - element = doc.find('input').eq(0); - expect(element.intlTelInput('getSelectedCountryData').iso2).toEqual('gb'); + + var iti = findLastCreatedItiInstance(); + expect(iti.getSelectedCountryData().iso2).toEqual('gb'); })); it('should set the country when model value is present with plus sign prefix', inject(function ($compile) { @@ -127,8 +129,9 @@ describe('ng-intl-tel-input', function () { ); $compile(doc)($scope); $scope.$digest(); - element = doc.find('input').eq(0); - expect(element.intlTelInput('getSelectedCountryData').iso2).toEqual('gb'); + + var iti = findLastCreatedItiInstance(); + expect(iti.getSelectedCountryData().iso2).toEqual('gb'); })); it('should apply the intl-tel-input jquery plugin to input fields without a type declaration', inject(function ($compile) { diff --git a/ng-intl-tel-input.provider.js b/ng-intl-tel-input.provider.js index c835f25..f5d1975 100644 --- a/ng-intl-tel-input.provider.js +++ b/ng-intl-tel-input.provider.js @@ -18,7 +18,7 @@ angular.module('ngIntlTelInput') if (!window.intlTelInputUtils) { $log.warn('intlTelInputUtils is not defined. Formatting and validation will not work.'); } - elm.intlTelInput(props); + return window.intlTelInput(elm[0], props); } }, }); diff --git a/ng-intl-tel-input.provider.spec.js b/ng-intl-tel-input.provider.spec.js index ef5a098..38ba8ce 100644 --- a/ng-intl-tel-input.provider.spec.js +++ b/ng-intl-tel-input.provider.spec.js @@ -8,34 +8,31 @@ describe('ngIntlTelInput Provider', function () { beforeEach(inject(function (_$injector_, $compile) { $injector = _$injector_; element = angular.element( - '
' + - '' + - '' + - '
' + '' ); })); it('should allow the passing of utils file', function () { var script = {'utilsScript': '/path/to/utils'}; provider.set(script); - var stub = sinon.stub(element, 'intlTelInput'); + var stub = sinon.stub(window, 'intlTelInput'); $injector.invoke(provider.$get).init(element); - expect(stub.calledWith(script)).toBe(true); + expect(stub.calledWith(element[0], script)).toBe(true); stub.restore(); }); it('should set initial country', function () { provider.set({'initialCountry': 'af'}); - $injector.invoke(provider.$get).init(element); - expect(element.intlTelInput('getSelectedCountryData').iso2).toEqual('af'); + var iti = $injector.invoke(provider.$get).init(element); + expect(iti.getSelectedCountryData().iso2).toEqual('af'); }); it('should set multiple properties', function () { var script = {'initialCountry': 'us', 'utilsScript': 'lol'}; provider.set(script); - var stub = sinon.stub(element, 'intlTelInput'); + var stub = sinon.stub(window, 'intlTelInput'); $injector.invoke(provider.$get).init(element); - expect(stub.calledWith(script)).toBe(true); + expect(stub.calledWith(element[0], script)).toBe(true); stub.restore(); }); }); @@ -59,7 +56,10 @@ describe('ngIntlTelInput Provider', function () { var input = element.find('input'); $compile(element)($rootScope); $rootScope.$digest(); - expect(input.intlTelInput('getSelectedCountryData').iso2).toEqual('af'); + + var iti = findLastCreatedItiInstance(); + + expect(iti.getSelectedCountryData().iso2).toEqual('af'); })); }); }); diff --git a/package.json b/package.json index 23f25a5..8ec9773 100644 --- a/package.json +++ b/package.json @@ -63,6 +63,6 @@ "uglify": "^0.1.5" }, "dependencies": { - "intl-tel-input": "~12.1.6" + "intl-tel-input": "^14.0.0" } } diff --git a/test-helpers.js b/test-helpers.js new file mode 100644 index 0000000..efe8036 --- /dev/null +++ b/test-helpers.js @@ -0,0 +1,9 @@ +/** + * Retrieves last created instance from global var window.intlTelInputGlobals + * because we can't get it from the DOM anymore + */ +var findLastCreatedItiInstance = function() +{ + var keys = Object.keys(window.intlTelInputGlobals.instances); + return window.intlTelInputGlobals.instances[keys[keys.length - 1]]; +}; \ No newline at end of file From b5d951868f735192110dbf0f34c1ff72fc597145 Mon Sep 17 00:00:00 2001 From: Damien Pitard Date: Fri, 5 Oct 2018 18:46:01 +0200 Subject: [PATCH 2/4] remove jquery --- bower.json | 2 +- index.html | 1 - karma.conf.js | 1 - ng-intl-tel-input.directive.spec.js | 39 +++++++++++++++++------------ package.json | 1 - 5 files changed, 24 insertions(+), 20 deletions(-) diff --git a/bower.json b/bower.json index 91d1213..d78189d 100644 --- a/bower.json +++ b/bower.json @@ -26,7 +26,7 @@ "tests" ], "dependencies": { - "intl-tel-input": "^14.0.0" + "intl-tel-input": "14.0.0" }, "devDependencies": { "angularjs": "^1.6.7", diff --git a/index.html b/index.html index acb3723..5819d3f 100644 --- a/index.html +++ b/index.html @@ -85,7 +85,6 @@

Setting default country with data-initial-country

- diff --git a/karma.conf.js b/karma.conf.js index 2ad9170..a7e09e1 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -19,7 +19,6 @@ module.exports = function (config) { ], files: [ - 'bower_components/jquery/dist/jquery.js', 'bower_components/angularjs/angular.js', 'bower_components/angular-mocks/angular-mocks.js', 'bower_components/intl-tel-input/build/js/intlTelInput.js', diff --git a/ng-intl-tel-input.directive.spec.js b/ng-intl-tel-input.directive.spec.js index 0e66097..2a374eb 100644 --- a/ng-intl-tel-input.directive.spec.js +++ b/ng-intl-tel-input.directive.spec.js @@ -14,15 +14,15 @@ describe('ng-intl-tel-input', function () { $compile(doc)($scope); $scope.$digest(); form = $scope.form; - element = doc.find('input').eq(0); + element = (doc.find('input').eq(0))[0]; })); - it('should apply the intl-tel-input jquery plugin to text fields', function () { - expect(doc.find('.intl-tel-input').length).toEqual(1); + it('should apply the intl-tel-input to text fields', function () { + expect(doc[0].querySelector('.intl-tel-input')).not.toBeNull(); }); - it('should apply the intl-tel-input jquery plugin to tel fields', inject(function ($compile, $rootScope) { + it('should apply the intl-tel-input to tel fields', inject(function ($compile, $rootScope) { doc = angular.element( '
' + '' + @@ -30,10 +30,10 @@ describe('ng-intl-tel-input', function () { ); $compile(doc)($scope); $scope.$digest(); - expect(doc.find('.intl-tel-input').length).toEqual(1); + expect(doc[0].querySelector('.intl-tel-input')).not.toBeNull(); })); - it('should apply the intl-tel-input jquery plugin to text and tel fields', inject(function ($compile, $rootScope) { + it('should apply the intl-tel-input to text and tel fields', inject(function ($compile, $rootScope) { doc = angular.element( '' + '' + @@ -44,51 +44,58 @@ describe('ng-intl-tel-input', function () { ); $compile(doc)($scope); $scope.$digest(); - expect(doc.find('.intl-tel-input').length).toEqual(0); + expect(doc[0].querySelector('.intl-tel-input')).toBeNull(); })); it('should set the field as invalid with bad input', function () { - angular.element(element).val('07400 123456').trigger('input'); + element.value = '07400 123456'; + element.dispatchEvent(new Event('input')); $scope.$digest(); expect(form.tel.$error.ngIntlTelInput).toBeDefined(); expect(form.tel.$valid).toBe(false); }); it('should set the field as invalid with input longer than > 0', function () { - angular.element(element).val('1').trigger('input'); + element.value = '1'; + element.dispatchEvent(new Event('input')); $scope.$digest(); expect(form.tel.$error.ngIntlTelInput).toBeDefined(); expect(form.tel.$valid).toBe(false); }); it('should set the field as valid with good input', function () { - angular.element(element).val('2103128425').trigger('input'); + element.value = '2103128425'; + element.dispatchEvent(new Event('input')); $scope.$digest(); expect(form.tel.$error.ngIntlTelInput).toBeUndefined(); expect(form.tel.$valid).toBe(true); }); it('should set the field as valid with empty input', function () { - angular.element(element).val('').trigger('input'); + element.value = ''; + element.dispatchEvent(new Event('input')); $scope.$digest(); expect(form.tel.$error.ngIntlTelInput).toBeUndefined(); expect(form.tel.$valid).toBe(true); }); it('should set the model value to the full phone number with dial code', function () { - angular.element(element).val('2103128425').trigger('input'); + element.value = '2103128425'; + element.dispatchEvent(new Event('input')); $scope.$digest(); expect($scope.model.tel).toEqual('+12103128425'); }); it('should set the model value to the full phone number with dial code and plus sign prefix', function () { - angular.element(element).val('+12103128425').trigger('input'); + element.value = '+12103128425'; + element.dispatchEvent(new Event('input')); $scope.$digest(); expect($scope.model.tel).toEqual('+12103128425'); }); it('should not set the model value when invalid', function () { - angular.element(element).val('07400 123456').trigger('input'); + element.value = '07400 123456'; + element.dispatchEvent(new Event('input')); $scope.$digest(); expect($scope.model.tel).toBeUndefined(); }); @@ -134,7 +141,7 @@ describe('ng-intl-tel-input', function () { expect(iti.getSelectedCountryData().iso2).toEqual('gb'); })); - it('should apply the intl-tel-input jquery plugin to input fields without a type declaration', inject(function ($compile) { + it('should apply the intl-tel-input to input fields without a type declaration', inject(function ($compile) { doc = angular.element( '' + '' + @@ -142,7 +149,7 @@ describe('ng-intl-tel-input', function () { ); $compile(doc)($scope); $scope.$digest(); - expect(doc.find('.intl-tel-input').length).toEqual(1); + expect(doc[0].querySelector('.intl-tel-input')).not.toBeNull(); })); it('should set the selected country data when data-selected-country attribute is present', inject(function ($compile) { diff --git a/package.json b/package.json index 8ec9773..7d5b012 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,6 @@ "concat-cli": "^4.0.0", "http-server": "^0.10.0", "jasmine-core": "^2.3.4", - "jquery": "^3.2.1", "jshint": "^2.9.5", "karma": "^1.7.1", "karma-chrome-launcher": "^2.2.0", From ed4194e2098460e749c075b95e29922005b14fdb Mon Sep 17 00:00:00 2001 From: Damien Pitard Date: Mon, 8 Oct 2018 13:15:47 +0200 Subject: [PATCH 3/4] Updated protractor to 5.4.1 to make end-to-end tests work --- .nvmrc | 1 + .travis.yml | 11 +++++------ README.md | 16 ++++++++++++++++ package.json | 2 +- protractor.conf.js | 7 +++++-- 5 files changed, 28 insertions(+), 9 deletions(-) create mode 100644 .nvmrc diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..6a1fccf --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +6.9.0 \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 34ff254..f11bea1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,18 +1,17 @@ language: node_js node_js: - - "iojs" - - "0.12" - - "4" - - "5" - "6" + - "7" + - "8" + - "9" + - "10" addons: - - "firefox": "36.0.4" + - "firefox": latest install: - "npm install" - "./node_modules/bower/bin/bower install" before_script: - "export DISPLAY=:99.0" - - "sh -e /etc/init.d/xvfb start" - "npm run start &" script: - "npm run jshint" diff --git a/README.md b/README.md index 817b750..82fdde6 100644 --- a/README.md +++ b/README.md @@ -79,3 +79,19 @@ This attribute allows run-time setting of the default country. ```html ``` + +## Running tests + +Be sure to install the right node version. You can use [nvm](https://github.com/creationix/nvm): + + nvm i + +Run Unit tests (jasmine): + + npm run test + +Run End-to-end tests (protractor): + + npm run webdriver-update + npm run start & + npm run protractor diff --git a/package.json b/package.json index 7d5b012..12711dc 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ "mversion": "^1.10.0", "phantomjs": "^1.9.17", "phantomjs-polyfills": "^1.0.1", - "protractor": "~2.4.0", + "protractor": "^5.4.1", "sinon": "^4.1.3", "uglify": "^0.1.5" }, diff --git a/protractor.conf.js b/protractor.conf.js index 3d79610..7fe2f76 100644 --- a/protractor.conf.js +++ b/protractor.conf.js @@ -2,7 +2,7 @@ var path = require('path'); exports.config = { rootElement: 'html', - seleniumServerJar: __dirname + '/node_modules/protractor/selenium/selenium-server-standalone-2.47.1.jar', + seleniumServerJar: __dirname + '/node_modules/protractor/node_modules/webdriver-manager/selenium/selenium-server-standalone-3.14.0.jar', // A base URL for your application under test. Calls to protractor.get() // with relative paths will be prepended with this. @@ -18,7 +18,10 @@ exports.config = { // Capabilities to be passed to the webdriver instance. capabilities: { - browserName: 'firefox' + browserName: 'firefox', + 'moz:firefoxOptions': { + 'args': ['-headless'] + } }, allScriptsTimeout: (1000 * 60 * 4), From 13ce1dd230af7b5bb0651383649e65a0384b54ce Mon Sep 17 00:00:00 2001 From: Damien Pitard Date: Mon, 8 Oct 2018 15:09:41 +0200 Subject: [PATCH 4/4] Downgrade gecko driver to make it work with firefox 56.0.2 (as used by travis) --- .travis.yml | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index f11bea1..06d3d4d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,7 @@ node_js: - "9" - "10" addons: - - "firefox": latest + - "firefox": latest-esr install: - "npm install" - "./node_modules/bower/bin/bower install" diff --git a/package.json b/package.json index 12711dc..c26869e 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "karma": "karma start karma.conf.js", "concat": "node ./node_modules/concat-cli/index.js -f ng-intl-tel-input.{module,provider,directive}.js -o dist/ng-intl-tel-input.js", "uglify": "uglify -s dist/ng-intl-tel-input.js -o dist/ng-intl-tel-input.min.js", - "webdriver-update": "webdriver-manager update", + "webdriver-update": "webdriver-manager update --versions.gecko=v0.19.1", "protractor": "protractor protractor.conf.js", "mversion": "mversion " },