Skip to content
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
1 change: 1 addition & 0 deletions src/reader-activation/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export const EVENTS = {
data: 'data',
activity: 'activity',
overlay: 'overlay',
segment: 'segment',
session: 'session',
};

Expand Down
2 changes: 2 additions & 0 deletions src/reader-activation/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { getPendingCheckout, setPendingCheckout } from './checkout.js';
import { EVENTS, on, off, emit } from './events.js';
import { getCookie, setCookie, generateID, debugLog } from './utils.js';
import overlays from './overlays.js';
import segments from './segments.js';
import initAnalytics from './analytics.js';
import setupArticleViewsAggregates from './article-view.js';
import setupEngagement from './engagement.js';
Expand Down Expand Up @@ -440,6 +441,7 @@ function attachNewsletterFormListener() {
const readerActivation = {
store,
overlays,
segments,
on,
off,
dispatchActivity,
Expand Down
74 changes: 74 additions & 0 deletions src/reader-activation/segments.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { EVENTS, emit } from './events';

let allSegments = {};
let matchedSegment = null;

/**
* Register segment definitions.
*
* @param {Object} segments Segments keyed by ID with { name, criteria, priority } values.
*/
function register( segments ) {
if ( ! segments || typeof segments !== 'object' ) {
return;
}
const hadMatch = matchedSegment && ! allSegments[ matchedSegment ];
allSegments = { ...allSegments, ...segments };
if ( hadMatch && allSegments[ matchedSegment ] ) {
emit( EVENTS.segment, { segmentId: matchedSegment, segment: allSegments[ matchedSegment ], all: { ...allSegments } } );
}
}

/**
* Set the matched segment for the current reader.
*
* @param {string|null} segmentId Segment ID or null to clear.
*
* @return {Object|null} Matched segment object or null.
*/
function setMatch( segmentId = null ) {
const normalizedId = segmentId !== null && segmentId !== undefined ? String( segmentId ) : null;
if ( normalizedId === matchedSegment ) {
return getMatch();
Comment thread
miguelpeixe marked this conversation as resolved.
}
matchedSegment = normalizedId;
const segment = matchedSegment ? allSegments[ matchedSegment ] || null : null;
emit( EVENTS.segment, { segmentId: matchedSegment, segment, all: { ...allSegments } } );
Comment thread
miguelpeixe marked this conversation as resolved.
return getMatch();
}

/**
* Get the matched segment.
*
* @return {Object|null} Matched segment object with id, or null.
*/
function getMatch() {
if ( ! matchedSegment || ! allSegments[ matchedSegment ] ) {
return null;
}
return { id: matchedSegment, ...allSegments[ matchedSegment ] };
}

/**
* Get all registered segments.
*
* @return {Object} Segments keyed by ID.
*/
function getAll() {
return { ...allSegments };
}

/**
* Reset module state. For testing only.
*/
export function reset() {
allSegments = {};
matchedSegment = null;
}

export default {
Comment thread
miguelpeixe marked this conversation as resolved.
register,
setMatch,
getMatch,
getAll,
};
81 changes: 81 additions & 0 deletions src/reader-activation/segments.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { on, off } from './events';
import segments, { reset } from './segments';

const sampleSegments = {
42: { name: 'Loyal Readers', criteria: [ { criteria_id: 'articles_read', value: { min: 5 } } ], priority: 0 },
43: { name: 'New Visitors', criteria: [ { criteria_id: 'articles_read', value: { max: 2 } } ], priority: 1 },
};

describe( 'segments', () => {
beforeEach( () => {
reset();
} );
it( 'should return empty object initially', () => {
expect( segments.getAll() ).toEqual( {} );
} );
it( 'should return null for match initially', () => {
expect( segments.getMatch() ).toBeNull();
} );
it( 'should register segments and retrieve them', () => {
segments.register( sampleSegments );
const all = segments.getAll();
expect( all[ '42' ].name ).toBe( 'Loyal Readers' );
expect( all[ '43' ].name ).toBe( 'New Visitors' );
} );
it( 'should set match and emit segment event', () => {
const callback = jest.fn();
on( 'segment', callback );
segments.register( sampleSegments );
segments.setMatch( '42' );
expect( callback ).toHaveBeenCalled();
const detail = callback.mock.calls[ 0 ][ 0 ].detail;
Comment thread
miguelpeixe marked this conversation as resolved.
expect( detail.segmentId ).toBe( '42' );
expect( detail.segment.name ).toBe( 'Loyal Readers' );
expect( detail.all ).toEqual( expect.objectContaining( { 42: expect.any( Object ) } ) );
off( 'segment', callback );
} );
it( 'should not re-emit when setting same match', () => {
const callback = jest.fn();
on( 'segment', callback );
segments.register( sampleSegments );
segments.setMatch( '42' );
callback.mockClear();
segments.setMatch( '42' );
expect( callback ).not.toHaveBeenCalled();
off( 'segment', callback );
} );
Comment thread
miguelpeixe marked this conversation as resolved.
it( 'should return matched segment via getMatch', () => {
segments.register( sampleSegments );
segments.setMatch( '42' );
const match = segments.getMatch();
expect( match.id ).toBe( '42' );
expect( match.name ).toBe( 'Loyal Readers' );
expect( match.priority ).toBe( 0 );
} );
it( 'should clear match and emit event', () => {
const callback = jest.fn();
on( 'segment', callback );
segments.register( sampleSegments );
segments.setMatch( '42' );
callback.mockClear();
segments.setMatch( null );
Comment thread
miguelpeixe marked this conversation as resolved.
expect( segments.getMatch() ).toBeNull();
expect( callback ).toHaveBeenCalled();
expect( callback.mock.calls[ 0 ][ 0 ].detail.segmentId ).toBeNull();
off( 'segment', callback );
} );
it( 'should re-emit when register resolves a pending match', () => {
const callback = jest.fn();
on( 'segment', callback );
segments.setMatch( '42' );
expect( segments.getMatch() ).toBeNull();
callback.mockClear();
segments.register( sampleSegments );
expect( callback ).toHaveBeenCalled();
const detail = callback.mock.calls[ 0 ][ 0 ].detail;
expect( detail.segmentId ).toBe( '42' );
expect( detail.segment.name ).toBe( 'Loyal Readers' );
expect( segments.getMatch().id ).toBe( '42' );
off( 'segment', callback );
} );
} );
Loading