Skip to content
This repository has been archived by the owner on Aug 17, 2021. It is now read-only.

Commit

Permalink
Merge pull request #176 from felixmosh/generate-link-ondemand
Browse files Browse the repository at this point in the history
Improvement: Generate recaptcha script on demand. closes #175
  • Loading branch information
mtrias authored Jan 18, 2017
2 parents c59c81e + 7bdc4ac commit 82d33e6
Show file tree
Hide file tree
Showing 7 changed files with 264 additions and 57 deletions.
15 changes: 1 addition & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,20 +41,7 @@ See [the demo file](demo/usage.html) for a quick usage example.

- First, you need to get a valid recaptcha key for your domain. Go to http://www.google.com/recaptcha.

- Include the reCaptcha [API](https://developers.google.com/recaptcha/docs/display#AJAX) using this script in your HTML:

```html
<script
src="https://www.google.com/recaptcha/api.js?onload=vcRecaptchaApiLoaded&render=explicit"
async defer
></script>
```

As you can see, we are specifying a `onload` callback, which will notify the angular service once the api is ready for usage.

The `onload` callback name defaults to `vcRecaptchaApiLoaded`, but can be overridden by the service provider via `vcRecaptchaServiceProvider.setOnLoadFunctionName('myOtherFunctionName');`.

- Also include the vc-recaptcha script and make your angular app depend on the `vcRecaptcha` module.
- Include the vc-recaptcha script and make your angular app depend on the `vcRecaptcha` module.

```html
<script type="text/javascript" src="angular-recaptcha.js"></script>
Expand Down
1 change: 1 addition & 0 deletions karma.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ module.exports = function (config) {
'src/module.js',
'src/*.js',

'tests/*.driver.js',
'tests/*_test.js'
],

Expand Down
9 changes: 8 additions & 1 deletion src/service.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@
provider.onLoadFunctionName = onLoadFunctionName;
};

provider.$get = ['$rootScope','$window', '$q', function ($rootScope, $window, $q) {
provider.$get = ['$rootScope','$window', '$q', '$document', function ($rootScope, $window, $q, $document) {
var deferred = $q.defer(), promise = deferred.promise, instances = {}, recaptcha;

$window.vcRecaptchaApiLoadedCallback = $window.vcRecaptchaApiLoadedCallback || [];
Expand Down Expand Up @@ -133,6 +133,13 @@
// Check if grecaptcha is not defined already.
if (ng.isDefined($window.grecaptcha)) {
callback();
} else {
// Generate link on demand
var script = $document.get(0).createElement('script');
script.async = true;
script.defer = true;
script.src = 'https://www.google.com/recaptcha/api.js?onload='+provider.onLoadFunctionName+'&render=explicit';
$document.get(0).body.appendChild(script);
}

return {
Expand Down
35 changes: 35 additions & 0 deletions tests/provider.driver.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
function ProviderDriver() {
var _this = this;
var mockModules = {
$window: {}
};

module(mockModules); // mock all the properties

this.given = {
recaptchaLoaded: function (recaptchaMock) {
mockModules.$window.grecaptcha = recaptchaMock;
return _this;
}
};

this.when = {
created: function () {
module(function (vcRecaptchaServiceProvider) {
_this.provider = vcRecaptchaServiceProvider;
});

inject(); // needed for angular-mocks to kick off

return _this;
},
callingCreate: function () {
inject(function (vcRecaptchaService, $rootScope) {
vcRecaptchaService.create(null, {});
$rootScope.$digest();
});

return this;
}
};
}
94 changes: 94 additions & 0 deletions tests/provider_test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
describe('provider', function () {
'use strict';

var driver,
recaptchaMock,
key;

beforeEach(module('vcRecaptcha'));

beforeEach(function () {
driver = new ProviderDriver();
driver.given.recaptchaLoaded(recaptchaMock = jasmine.createSpyObj('recaptchaMock', ['render']))
.when.created();

driver.provider.setSiteKey(key = '1234567890123456789012345678901234567890');
});

it('should setDefaults', function () {
var modifiedKey = key.substring(0, 39) + 'x';

driver.provider.setDefaults({key: modifiedKey});

driver.when.callingCreate();

var callArgs = recaptchaMock.render.calls.mostRecent().args[1];

expect(callArgs).toEqual(jasmine.objectContaining({sitekey: modifiedKey}));
});

it('should setSiteKey', function () {
driver.provider.setSiteKey(key);

driver.when.callingCreate();

var callArgs = recaptchaMock.render.calls.mostRecent().args[1];

expect(callArgs).toEqual(jasmine.objectContaining({sitekey: key}));
});

it('should setTheme', function () {
var theme = 'theme';
driver.provider.setTheme(theme);

driver.when.callingCreate();

var callArgs = recaptchaMock.render.calls.mostRecent().args[1];

expect(callArgs).toEqual(jasmine.objectContaining({theme: theme}));
});

it('should setStoken', function () {
var stoken = 'stoken';
driver.provider.setStoken(stoken);

driver.when.callingCreate();

var callArgs = recaptchaMock.render.calls.mostRecent().args[1];

expect(callArgs).toEqual(jasmine.objectContaining({stoken: stoken}));
});

it('should setSize', function () {
var size = 'size';
driver.provider.setSize(size);

driver.when.callingCreate();

var callArgs = recaptchaMock.render.calls.mostRecent().args[1];

expect(callArgs).toEqual(jasmine.objectContaining({size: size}));
});

it('should setType', function () {
var type = 'type';
driver.provider.setType(type);

driver.when.callingCreate();

var callArgs = recaptchaMock.render.calls.mostRecent().args[1];

expect(callArgs).toEqual(jasmine.objectContaining({type: type}));
});

it('should setLang', function () {
var lang = 'en';
driver.provider.setLang(lang);

driver.when.callingCreate();

var callArgs = recaptchaMock.render.calls.mostRecent().args[1];

expect(callArgs).toEqual(jasmine.objectContaining({hl: lang}));
});
});
46 changes: 46 additions & 0 deletions tests/service.driver.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
function ServiceDriver() {
var _this = this;
var mockModules = {
$window: {},
$document: {}
};

module(mockModules); // mock all the properties

this.given = {
apiLoaded: function (mockRecaptcha) {
mockModules.$window.grecaptcha = mockRecaptcha;

return _this;
},
onLoadFunctionName: function (funcName) {
module(function (vcRecaptchaServiceProvider) {
vcRecaptchaServiceProvider.setOnLoadFunctionName(funcName);
});
return _this;
},
mockDocument: function (mockDocument) {
mockModules.$document.get = mockDocument.get;

return _this;
}
};

this.when = {
created: function () {
inject(function (vcRecaptchaService) {
_this.service = vcRecaptchaService;
})
},
notifyThatApiLoaded: function () {
mockModules.$window.vcRecaptchaApiLoaded();
return _this;
}
};
}

ServiceDriver.prototype.applyChanges = function () {
inject(function ($rootScope) {
$rootScope.$digest();
});
};
121 changes: 79 additions & 42 deletions tests/service_test.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,27 @@
describe('service', function () {
'use strict';

var vcRecaptchaService, $window;
var driver;

beforeEach(module('vcRecaptcha', function ($provide) {
$provide.constant('$window', {
grecaptcha: jasmine.createSpyObj('grecaptcha', ['render', 'getResponse', 'reset'])
});
}));
beforeEach(module('vcRecaptcha'));

beforeEach(inject(function (_vcRecaptchaService_, _$window_) {
vcRecaptchaService = _vcRecaptchaService_;
$window = _$window_;
beforeEach(function () {
driver = new ServiceDriver();
});

$window.vcRecaptchaApiLoaded = jasmine.createSpy('vcRecaptchaApiLoaded');
}));
describe('with loaded api', function () {
var grecaptchaMock;

describe('create', function () {
it('should call recaptcha.render', inject(function ($rootScope) {
var _element = '<div></div>',
_key = '1234567890123456789012345678901234567890',
_fn = angular.noop,
beforeEach(function () {
driver
.given.apiLoaded(grecaptchaMock = jasmine.createSpyObj('grecaptcha', ['render', 'getResponse', 'reset']))
.when.created();
});

it('should call recaptcha.render', function () {
var _element = '<div></div>',
_key = '1234567890123456789012345678901234567890',
_fn = angular.noop,
_confRender = {
sitekey: _key,
key: _key,
Expand All @@ -32,57 +33,93 @@ describe('service', function () {
hl: undefined
};

$window.vcRecaptchaApiLoaded();
driver.when.notifyThatApiLoaded();

vcRecaptchaService.create(_element, {
driver.service.create(_element, {
key: _confRender.key,
callback: _fn
});

$rootScope.$digest();
driver.applyChanges();

expect($window.grecaptcha.render).toHaveBeenCalledWith(_element, _confRender);
}));
});
expect(grecaptchaMock.render).toHaveBeenCalledWith(_element, _confRender);
});

describe('reload', function () {
it('should call reset', function () {
var _widgetId = 123;

vcRecaptchaService.reload(_widgetId);
driver.service.reload(_widgetId);

expect($window.grecaptcha.reset).toHaveBeenCalledWith(_widgetId);
expect(grecaptchaMock.reset).toHaveBeenCalledWith(_widgetId);
});
});

describe('getResponse', function () {
it('should call getResponse', function () {
var _widgetId = 123;

vcRecaptchaService.getResponse(_widgetId);
driver.service.getResponse(_widgetId);

expect($window.grecaptcha.getResponse).toHaveBeenCalledWith(_widgetId);
expect(grecaptchaMock.getResponse).toHaveBeenCalledWith(_widgetId);
});
});

describe('useLang', function () {
it('should call useLang', inject(function ($rootScope) {
var _element = angular.element('<div><iframe src="http://localhost?hl=fr"></iframe></div>')[0],
_key = '1234567890123456789012345678901234567890';
it('should call useLang', function () {
var _element = angular.element('<div><iframe src="http://localhost?hl=fr"></iframe></div>')[0],
_key = '1234567890123456789012345678901234567890';

$window.vcRecaptchaApiLoaded();
driver.when.notifyThatApiLoaded();

vcRecaptchaService.create(_element, {
driver.service.create(_element, {
key: _key
}).then(function (widgetId) {
var instance = vcRecaptchaService.getInstance(widgetId);
var instance = driver.service.getInstance(widgetId);
expect(instance).toEqual(_element);

vcRecaptchaService.useLang(widgetId, 'es');
expect(vcRecaptchaService.useLang(widgetId)).toEqual('es');
})
driver.service.useLang(widgetId, 'es');
expect(driver.service.useLang(widgetId)).toEqual('es');
});

driver.applyChanges();
});
});

describe('without loaded api', function () {
var scriptTagSpy,
appendChildSpy,
funcName;

beforeEach(function () {
scriptTagSpy = jasmine.createSpy('scriptTagSpy');
appendChildSpy = jasmine.createSpy('appendChildSpy');

driver
.given.onLoadFunctionName(funcName = 'my-func')
.given.mockDocument({
get: function () {
return {
createElement: function () {
return scriptTagSpy;
},
body: {
appendChild: appendChildSpy
}
};
}
})
.when.created();

});

$rootScope.$digest();
}));
it('should add script tag to body', function () {
expect(scriptTagSpy.async).toBe(true);
expect(scriptTagSpy.defer).toBe(true);
expect(appendChildSpy).toHaveBeenCalledWith(scriptTagSpy);
});

it('should add callback function name to src', function () {
expect(scriptTagSpy.src).toBe('https://www.google.com/recaptcha/api.js?onload=' + funcName + '&render=explicit');
});

it('should validate that recaptcha is loaded', function () {
expect(driver.service.reload).toThrowError('reCaptcha has not been loaded yet.');
});
});
});

0 comments on commit 82d33e6

Please sign in to comment.