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

Improvement: Generate recaptcha script on demand. closes #175 #176

Merged
merged 4 commits into from
Jan 18, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.');
});
});
});