Unverified Commit 743ea231 authored by Robert Knight's avatar Robert Knight Committed by GitHub

Merge pull request #1151 from hypothesis/warn-less

Replace several uses of `console.warn` with `warnOnce`
parents 23abae29 ac91921b
'use strict'; 'use strict';
const events = require('../shared/bridge-events'); const events = require('../shared/bridge-events');
const warnOnce = require('../shared/warn-once');
let _features = {}; let _features = {};
...@@ -19,7 +20,7 @@ module.exports = { ...@@ -19,7 +20,7 @@ module.exports = {
flagEnabled: function(flag) { flagEnabled: function(flag) {
if (!(flag in _features)) { if (!(flag in _features)) {
console.warn('looked up unknown feature', flag); warnOnce('looked up unknown feature', flag);
return false; return false;
} }
return _features[flag]; return _features[flag];
......
...@@ -5,6 +5,8 @@ const features = require('../features'); ...@@ -5,6 +5,8 @@ const features = require('../features');
describe('features - annotation layer', function() { describe('features - annotation layer', function() {
let featureFlagsUpdateHandler; let featureFlagsUpdateHandler;
let fakeWarnOnce;
const initialFeatures = { const initialFeatures = {
feature_on: true, feature_on: true,
feature_off: false, feature_off: false,
...@@ -15,7 +17,10 @@ describe('features - annotation layer', function() { ...@@ -15,7 +17,10 @@ describe('features - annotation layer', function() {
}; };
beforeEach(function() { beforeEach(function() {
sinon.stub(console, 'warn'); fakeWarnOnce = sinon.stub();
features.$imports.$mock({
'../shared/warn-once': fakeWarnOnce,
});
features.init({ features.init({
on: function(topic, handler) { on: function(topic, handler) {
...@@ -30,8 +35,8 @@ describe('features - annotation layer', function() { ...@@ -30,8 +35,8 @@ describe('features - annotation layer', function() {
}); });
afterEach(function() { afterEach(function() {
console.warn.restore();
features.reset(); features.reset();
features.$imports.$restore();
}); });
describe('flagEnabled', function() { describe('flagEnabled', function() {
...@@ -51,10 +56,9 @@ describe('features - annotation layer', function() { ...@@ -51,10 +56,9 @@ describe('features - annotation layer', function() {
}); });
it('should warn when accessing unknown flags', function() { it('should warn when accessing unknown flags', function() {
assert.notCalled(console.warn);
assert.isFalse(features.flagEnabled('unknown_feature')); assert.isFalse(features.flagEnabled('unknown_feature'));
assert.calledOnce(console.warn); assert.calledOnce(fakeWarnOnce);
assert.calledWith(console.warn, 'looked up unknown feature'); assert.calledWith(fakeWarnOnce, 'looked up unknown feature');
}); });
}); });
}); });
...@@ -23,4 +23,15 @@ describe('warnOnce', () => { ...@@ -23,4 +23,15 @@ describe('warnOnce', () => {
warnOnce('something else is wrong'); warnOnce('something else is wrong');
assert.calledWith(console.warn, 'something else is wrong'); assert.calledWith(console.warn, 'something else is wrong');
}); });
it('supports multiple arguments', () => {
warnOnce('foo', 'bar', 'baz');
assert.calledWith(console.warn, 'foo', 'bar', 'baz');
});
it('supports non-string arguments', () => {
const obj = {};
warnOnce(1, {}, false);
assert.calledWith(console.warn, 1, obj, false);
});
}); });
...@@ -8,14 +8,18 @@ let shownWarnings = {}; ...@@ -8,14 +8,18 @@ let shownWarnings = {};
* This is useful to avoid spamming the console if a warning is emitted in a * This is useful to avoid spamming the console if a warning is emitted in a
* context that may be called frequently. * context that may be called frequently.
* *
* @param {string} warning * @param {...any} args -
* Arguments to forward to `console.warn`. The arguments `toString()` values
* are concatenated into a string key which is used to determine if the warning
* has been logged before.
*/ */
function warnOnce(warning) { function warnOnce(...args) {
if (warning in shownWarnings) { const key = args.join();
if (key in shownWarnings) {
return; return;
} }
console.warn(warning); console.warn(...args);
shownWarnings[warning] = true; shownWarnings[key] = true;
} }
warnOnce.reset = () => { warnOnce.reset = () => {
......
...@@ -13,9 +13,10 @@ ...@@ -13,9 +13,10 @@
const events = require('../events'); const events = require('../events');
const bridgeEvents = require('../../shared/bridge-events'); const bridgeEvents = require('../../shared/bridge-events');
const warnOnce = require('../../shared/warn-once');
// @ngInject // @ngInject
function features($log, $rootScope, bridge, session) { function features($rootScope, bridge, session) {
const _sendFeatureFlags = function() { const _sendFeatureFlags = function() {
const userFeatures = session.state.features; const userFeatures = session.state.features;
bridge.call(bridgeEvents.FEATURE_FLAGS_UPDATED, userFeatures || {}); bridge.call(bridgeEvents.FEATURE_FLAGS_UPDATED, userFeatures || {});
...@@ -50,7 +51,7 @@ function features($log, $rootScope, bridge, session) { ...@@ -50,7 +51,7 @@ function features($log, $rootScope, bridge, session) {
const features = session.state.features; const features = session.state.features;
if (!(flag in features)) { if (!(flag in features)) {
$log.warn('looked up unknown feature', flag); warnOnce('looked up unknown feature', flag);
return false; return false;
} }
return features[flag]; return features[flag];
......
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
const queryString = require('query-string'); const queryString = require('query-string');
const uuid = require('node-uuid'); const uuid = require('node-uuid');
const warnOnce = require('../../shared/warn-once');
const events = require('../events'); const events = require('../events');
const Socket = require('../websocket'); const Socket = require('../websocket');
...@@ -103,7 +105,7 @@ function Streamer( ...@@ -103,7 +105,7 @@ function Streamer(
} }
function handleSocketOnError(event) { function handleSocketOnError(event) {
console.warn('Error connecting to H push notification service:', event); warnOnce('Error connecting to H push notification service:', event);
// In development, warn if the connection failure might be due to // In development, warn if the connection failure might be due to
// the app's origin not having been whitelisted in the H service's config. // the app's origin not having been whitelisted in the H service's config.
...@@ -112,7 +114,7 @@ function Streamer( ...@@ -112,7 +114,7 @@ function Streamer(
// HTTP status code for HTTP -> WS upgrade requests. // HTTP status code for HTTP -> WS upgrade requests.
const websocketHost = new URL(settings.websocketUrl).hostname; const websocketHost = new URL(settings.websocketUrl).hostname;
if (['localhost', '127.0.0.1'].indexOf(websocketHost) !== -1) { if (['localhost', '127.0.0.1'].indexOf(websocketHost) !== -1) {
console.warn( warnOnce(
'Check that your H service is configured to allow ' + 'Check that your H service is configured to allow ' +
'WebSocket connections from ' + 'WebSocket connections from ' +
window.location.origin window.location.origin
...@@ -144,7 +146,7 @@ function Streamer( ...@@ -144,7 +146,7 @@ function Streamer(
); );
} }
} else { } else {
console.warn('received unsupported notification', message.type); warnOnce('received unsupported notification', message.type);
} }
}); });
} }
......
...@@ -6,7 +6,7 @@ const bridgeEvents = require('../../../shared/bridge-events'); ...@@ -6,7 +6,7 @@ const bridgeEvents = require('../../../shared/bridge-events');
describe('h:features - sidebar layer', function() { describe('h:features - sidebar layer', function() {
let fakeBridge; let fakeBridge;
let fakeLog; let fakeWarnOnce;
let fakeRootScope; let fakeRootScope;
let fakeSession; let fakeSession;
let sandbox; let sandbox;
...@@ -18,9 +18,7 @@ describe('h:features - sidebar layer', function() { ...@@ -18,9 +18,7 @@ describe('h:features - sidebar layer', function() {
call: sinon.stub(), call: sinon.stub(),
}; };
fakeLog = { fakeWarnOnce = sinon.stub();
warn: sinon.stub(),
};
fakeRootScope = { fakeRootScope = {
eventCallbacks: {}, eventCallbacks: {},
...@@ -41,54 +39,39 @@ describe('h:features - sidebar layer', function() { ...@@ -41,54 +39,39 @@ describe('h:features - sidebar layer', function() {
}, },
}, },
}; };
features.$imports.$mock({
'../../shared/warn-once': fakeWarnOnce,
});
}); });
afterEach(function() { afterEach(function() {
features.$imports.$restore();
sandbox.restore(); sandbox.restore();
}); });
describe('flagEnabled', function() { describe('flagEnabled', function() {
it('should retrieve features data', function() { it('should retrieve features data', function() {
const features_ = features( const features_ = features(fakeRootScope, fakeBridge, fakeSession);
fakeLog,
fakeRootScope,
fakeBridge,
fakeSession
);
assert.equal(features_.flagEnabled('feature_on'), true); assert.equal(features_.flagEnabled('feature_on'), true);
assert.equal(features_.flagEnabled('feature_off'), false); assert.equal(features_.flagEnabled('feature_off'), false);
}); });
it('should return false if features have not been loaded', function() { it('should return false if features have not been loaded', function() {
const features_ = features( const features_ = features(fakeRootScope, fakeBridge, fakeSession);
fakeLog,
fakeRootScope,
fakeBridge,
fakeSession
);
// simulate feature data not having been loaded yet // simulate feature data not having been loaded yet
fakeSession.state = {}; fakeSession.state = {};
assert.equal(features_.flagEnabled('feature_on'), false); assert.equal(features_.flagEnabled('feature_on'), false);
}); });
it('should trigger a refresh of session data', function() { it('should trigger a refresh of session data', function() {
const features_ = features( const features_ = features(fakeRootScope, fakeBridge, fakeSession);
fakeLog,
fakeRootScope,
fakeBridge,
fakeSession
);
features_.flagEnabled('feature_on'); features_.flagEnabled('feature_on');
assert.calledOnce(fakeSession.load); assert.calledOnce(fakeSession.load);
}); });
it('should return false for unknown flags', function() { it('should return false for unknown flags', function() {
const features_ = features( const features_ = features(fakeRootScope, fakeBridge, fakeSession);
fakeLog,
fakeRootScope,
fakeBridge,
fakeSession
);
assert.isFalse(features_.flagEnabled('unknown_feature')); assert.isFalse(features_.flagEnabled('unknown_feature'));
}); });
}); });
...@@ -97,7 +80,7 @@ describe('h:features - sidebar layer', function() { ...@@ -97,7 +80,7 @@ describe('h:features - sidebar layer', function() {
assert.notProperty(fakeRootScope.eventCallbacks, events.USER_CHANGED); assert.notProperty(fakeRootScope.eventCallbacks, events.USER_CHANGED);
assert.notProperty(fakeRootScope.eventCallbacks, events.FRAME_CONNECTED); assert.notProperty(fakeRootScope.eventCallbacks, events.FRAME_CONNECTED);
features(fakeLog, fakeRootScope, fakeBridge, fakeSession); features(fakeRootScope, fakeBridge, fakeSession);
assert.property(fakeRootScope.eventCallbacks, events.USER_CHANGED); assert.property(fakeRootScope.eventCallbacks, events.USER_CHANGED);
assert.property(fakeRootScope.eventCallbacks, events.FRAME_CONNECTED); assert.property(fakeRootScope.eventCallbacks, events.FRAME_CONNECTED);
......
...@@ -82,7 +82,7 @@ describe('useStore', () => { ...@@ -82,7 +82,7 @@ describe('useStore', () => {
it('warns if the callback always returns a different value', () => { it('warns if the callback always returns a different value', () => {
const warnOnce = sinon.stub(); const warnOnce = sinon.stub();
$imports.$mock({ $imports.$mock({
'../util/warn-once': warnOnce, '../../shared/warn-once': warnOnce,
}); });
const BuggyComponent = () => { const BuggyComponent = () => {
// The result of the callback is an object with an `aValue` property // The result of the callback is an object with an `aValue` property
......
...@@ -6,7 +6,7 @@ const shallowEqual = require('shallowequal'); ...@@ -6,7 +6,7 @@ const shallowEqual = require('shallowequal');
const { useEffect, useRef, useReducer } = require('preact/hooks'); const { useEffect, useRef, useReducer } = require('preact/hooks');
const { useService } = require('../util/service-context'); const { useService } = require('../util/service-context');
const warnOnce = require('../util/warn-once'); const warnOnce = require('../../shared/warn-once');
/** /**
* Hook for accessing state or actions from the store inside a component. * Hook for accessing state or actions from the store inside a component.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment