@salesforcedevs/sfdocs-e2e 1.0.11-toolbar.2 → 1.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/content-repo/specs/sitemap.test.ts +0 -84
- package/src/shared/specs/contentActionToolbar.test.ts +25 -130
- package/src/shared/specs/docsContentType.test.ts +0 -287
- package/src/shared/specs/liquid-advanced.test.ts +0 -54
- package/src/shared/specs/liquid.test.ts +0 -97
- package/src/shared/pageobjects/content-action-toolbar.ts +0 -101
package/package.json
CHANGED
|
@@ -40,90 +40,6 @@ describe('Test Sitemap Routes', () => {
|
|
|
40
40
|
);
|
|
41
41
|
});
|
|
42
42
|
|
|
43
|
-
it('lists each docs markdown page once and each spec page without a .html extension', async () => {
|
|
44
|
-
await HomePage.open('sitemap.xml');
|
|
45
|
-
const data = await browser.getSitemap(
|
|
46
|
-
'http://localhost:3000/internal-sitemap-contents-1.xml',
|
|
47
|
-
);
|
|
48
|
-
|
|
49
|
-
// Markdown topics are published with a .html extension.
|
|
50
|
-
expect(data.body).toContain('<loc>/docs/docs-content-type-example/get-started.html</loc>');
|
|
51
|
-
expect(data.body).toContain(
|
|
52
|
-
'<loc>/docs/docs-content-type-example/widget-concepts.html</loc>',
|
|
53
|
-
);
|
|
54
|
-
expect(data.body).toContain(
|
|
55
|
-
'<loc>/docs/docs-content-type-example/beta-bulk-import.html</loc>',
|
|
56
|
-
);
|
|
57
|
-
|
|
58
|
-
// Spec (Redoc) topics are published without an extension.
|
|
59
|
-
expect(data.body).toContain('<loc>/docs/docs-content-type-example/sample-api</loc>');
|
|
60
|
-
expect(data.body).toContain('<loc>/docs/docs-content-type-example/new-sample-api</loc>');
|
|
61
|
-
|
|
62
|
-
// Each markdown URL appears exactly once.
|
|
63
|
-
const occurrences =
|
|
64
|
-
data.body.split('<loc>/docs/docs-content-type-example/get-started.html</loc>').length -
|
|
65
|
-
1;
|
|
66
|
-
expect(occurrences).toBe(1);
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it('excludes the external-link topic from the docs sitemap', async () => {
|
|
70
|
-
await HomePage.open('sitemap.xml');
|
|
71
|
-
const data = await browser.getSitemap(
|
|
72
|
-
'http://localhost:3000/internal-sitemap-contents-1.xml',
|
|
73
|
-
);
|
|
74
|
-
|
|
75
|
-
// externalLink entries never become routes, so they can't reach the sitemap.
|
|
76
|
-
expect(data.body).not.toContain('developer.salesforce.com');
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it('lists the deep-nesting docs project pages and spec in the sitemap', async () => {
|
|
80
|
-
await HomePage.open('sitemap.xml');
|
|
81
|
-
const data = await browser.getSitemap(
|
|
82
|
-
'http://localhost:3000/internal-sitemap-contents-1.xml',
|
|
83
|
-
);
|
|
84
|
-
|
|
85
|
-
expect(data.body).toContain('<loc>/docs/docs-deep-nesting-example/introduction.html</loc>');
|
|
86
|
-
expect(data.body).toContain('<loc>/docs/docs-deep-nesting-example/install.html</loc>');
|
|
87
|
-
expect(data.body).toContain('<loc>/docs/docs-deep-nesting-example/workspace.html</loc>');
|
|
88
|
-
expect(data.body).toContain('<loc>/docs/docs-deep-nesting-example/example-api</loc>');
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
it('lists a localized docs page but omits pages excluded from the ja-jp menu', async () => {
|
|
92
|
-
await HomePage.open('sitemap.xml');
|
|
93
|
-
const data = await browser.getSitemap(
|
|
94
|
-
'http://localhost:3000/internal-sitemap-contents-1.xml',
|
|
95
|
-
);
|
|
96
|
-
|
|
97
|
-
// ja-jp topics that are configured in the ja-jp TOC are legitimately in the sitemap.
|
|
98
|
-
expect(data.body).toContain(
|
|
99
|
-
'<loc>/docs/ja-jp/docs-content-type-example/get-started.html</loc>',
|
|
100
|
-
);
|
|
101
|
-
|
|
102
|
-
// englishonly pages are never in the ja-jp menu, so they must not appear under /ja-jp/.
|
|
103
|
-
expect(data.body).not.toContain(
|
|
104
|
-
'<loc>/docs/ja-jp/docs-content-type-example/us-compliance-notes.html</loc>',
|
|
105
|
-
);
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
it('serves the noindex robots metatag on the docs home URL but not on a real topic', async () => {
|
|
109
|
-
await browser.addCommand('getHtml', async (url: any) => {
|
|
110
|
-
const data = await got(url);
|
|
111
|
-
return data;
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
// The project home URL renders the first topic as a landing page with noindex.
|
|
115
|
-
const home = await browser.getHtml('http://localhost:3000/docs/docs-content-type-example');
|
|
116
|
-
expect(home.body).toContain('<meta name="robots" content="noindex,nofollow,nosnippet"/>');
|
|
117
|
-
|
|
118
|
-
// The same topic requested at its real URL must be indexable.
|
|
119
|
-
const topic = await browser.getHtml(
|
|
120
|
-
'http://localhost:3000/docs/docs-content-type-example/get-started.html',
|
|
121
|
-
);
|
|
122
|
-
expect(topic.body).not.toContain(
|
|
123
|
-
'<meta name="robots" content="noindex,nofollow,nosnippet"/>',
|
|
124
|
-
);
|
|
125
|
-
});
|
|
126
|
-
|
|
127
43
|
it('Read Coveo sitemap.xml', async () => {
|
|
128
44
|
await browser.addCommand('getCoveoSitemap', async (url: any) => {
|
|
129
45
|
const data = await got(url, {
|
|
@@ -1,9 +1,5 @@
|
|
|
1
|
-
import DocsPage from '../pageobjects/docs.page';
|
|
2
1
|
import GuidePage from '../pageobjects/guide.page';
|
|
3
2
|
import { ReferencePage } from '../pageobjects/reference.page';
|
|
4
|
-
import contentActionToolbar from '../pageobjects/content-action-toolbar';
|
|
5
|
-
|
|
6
|
-
const DOCS_MARKDOWN_PATH = '/docs/docs-content-type-example/get-started.html';
|
|
7
3
|
|
|
8
4
|
const EN_US_GUIDE_PATH = '/docs/service/vscode-maintainer/guide/vscode-overview.html';
|
|
9
5
|
const EN_US_MARKDOWN_REFERENCE_PATH =
|
|
@@ -11,154 +7,53 @@ const EN_US_MARKDOWN_REFERENCE_PATH =
|
|
|
11
7
|
const NON_MARKDOWN_REFERENCE_PATH = '/docs/test-metamind-oas-api/references/1mkfx9?meta=Summary';
|
|
12
8
|
const NON_EN_US_MARKDOWN_REFERENCE_PATH =
|
|
13
9
|
'/docs/commerce/ja-jp/flex-references-loc/references/manual-ref/commerce-cloud-api-overview.html';
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
26
|
-
const AI_ASSISTANTS = [
|
|
27
|
-
{ label: 'Open in ChatGPT', host: 'chatgpt.com' },
|
|
28
|
-
{ label: 'Open in Claude', host: 'claude.ai' },
|
|
29
|
-
{ label: 'Open in Perplexity', host: 'www.perplexity.ai' },
|
|
30
|
-
];
|
|
31
|
-
const LLMS_TXT_PATH = '/docs/llms.txt';
|
|
32
|
-
const COPIED_TOOLTIP_LABEL = 'Copied!';
|
|
10
|
+
|
|
11
|
+
async function expectToolbarButtons(toolbar: WebdriverIO.Element): Promise<void> {
|
|
12
|
+
const toolbarButtons = await toolbar.shadow$$('dx-button.toolbar-button');
|
|
13
|
+
expect(toolbarButtons.length).toBe(3);
|
|
14
|
+
|
|
15
|
+
const labels = await Promise.all(
|
|
16
|
+
toolbarButtons.map(async (button) => (await button.getText()).trim()),
|
|
17
|
+
);
|
|
18
|
+
expect(labels).toEqual(
|
|
19
|
+
expect.arrayContaining(['Copy as Markdown', 'View as Markdown', 'Copy URL to Markdown']),
|
|
20
|
+
);
|
|
21
|
+
}
|
|
33
22
|
|
|
34
23
|
describe('Content action toolbar scenarios', () => {
|
|
35
|
-
it('should
|
|
24
|
+
it('Markdown guide should have the toolbar', async () => {
|
|
36
25
|
const page = new GuidePage(EN_US_GUIDE_PATH);
|
|
37
26
|
page.open();
|
|
38
27
|
await page.waitPageLoad();
|
|
39
28
|
|
|
40
|
-
const toolbar = await
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
);
|
|
29
|
+
const toolbar = await $('>>>doc-content-action-toolbar');
|
|
30
|
+
await toolbar.waitForDisplayed();
|
|
31
|
+
await expectToolbarButtons(toolbar);
|
|
44
32
|
});
|
|
45
33
|
|
|
46
|
-
it('should
|
|
34
|
+
it('Markdown reference should have the toolbar', async () => {
|
|
47
35
|
const referencePage = new ReferencePage(EN_US_MARKDOWN_REFERENCE_PATH);
|
|
48
36
|
referencePage.open();
|
|
49
37
|
await referencePage.waitForMarkdownBasedReferencePageLoad();
|
|
50
38
|
|
|
51
|
-
const toolbar = await
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
);
|
|
39
|
+
const toolbar = await $('>>>doc-content-action-toolbar');
|
|
40
|
+
await toolbar.waitForDisplayed();
|
|
41
|
+
await expectToolbarButtons(toolbar);
|
|
55
42
|
});
|
|
56
43
|
|
|
57
|
-
it(
|
|
58
|
-
const page = new DocsPage(DOCS_MARKDOWN_PATH);
|
|
59
|
-
page.open();
|
|
60
|
-
await page.waitPageLoad();
|
|
61
|
-
|
|
62
|
-
const toolbar = await contentActionToolbar.get();
|
|
63
|
-
expect(await contentActionToolbar.getInlineButtonLabels(toolbar)).toEqual(
|
|
64
|
-
expect.arrayContaining(INLINE_MARKDOWN_LABELS),
|
|
65
|
-
);
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
it('should expose the full grouped action set through the More actions menu on a markdown page', async () => {
|
|
69
|
-
const page = new GuidePage(EN_US_GUIDE_PATH);
|
|
70
|
-
page.open();
|
|
71
|
-
await page.waitPageLoad();
|
|
72
|
-
|
|
73
|
-
const toolbar = await contentActionToolbar.get();
|
|
74
|
-
await contentActionToolbar.openMoreActionsMenu(toolbar);
|
|
75
|
-
|
|
76
|
-
const labelsByGroup = await contentActionToolbar.getMenuOptionLabelsByGroup(toolbar);
|
|
77
|
-
expect(Object.keys(labelsByGroup)).toEqual(
|
|
78
|
-
expect.arrayContaining(Object.keys(MENU_GROUPS)),
|
|
79
|
-
);
|
|
80
|
-
for (const [group, expectedLabels] of Object.entries(MENU_GROUPS)) {
|
|
81
|
-
expect(labelsByGroup[group]).toEqual(expect.arrayContaining(expectedLabels));
|
|
82
|
-
}
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
AI_ASSISTANTS.forEach(({ label, host }) => {
|
|
86
|
-
it(`should open ${host} with the page context when selecting ${label}`, async () => {
|
|
87
|
-
const page = new GuidePage(EN_US_GUIDE_PATH);
|
|
88
|
-
page.open();
|
|
89
|
-
await page.waitPageLoad();
|
|
90
|
-
|
|
91
|
-
const toolbar = await contentActionToolbar.get();
|
|
92
|
-
await contentActionToolbar.openMoreActionsMenu(toolbar);
|
|
93
|
-
|
|
94
|
-
const url = await contentActionToolbar.getUrlFromNewTab(() =>
|
|
95
|
-
contentActionToolbar.clickMenuOption(toolbar, label),
|
|
96
|
-
);
|
|
97
|
-
expect(url).toContain(host);
|
|
98
|
-
});
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
it('should open the markdown version of the page in a new tab when selecting View as Markdown', async () => {
|
|
102
|
-
const page = new GuidePage(EN_US_GUIDE_PATH);
|
|
103
|
-
page.open();
|
|
104
|
-
await page.waitPageLoad();
|
|
105
|
-
|
|
106
|
-
const toolbar = await contentActionToolbar.get();
|
|
107
|
-
await contentActionToolbar.openMoreActionsMenu(toolbar);
|
|
108
|
-
|
|
109
|
-
const url = await contentActionToolbar.getUrlFromNewTab(() =>
|
|
110
|
-
contentActionToolbar.clickMenuOption(toolbar, 'View as Markdown'),
|
|
111
|
-
);
|
|
112
|
-
expect(url.endsWith('.md')).toBe(true);
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
it('should open the site llms.txt in a new tab when selecting View llms.txt', async () => {
|
|
116
|
-
const page = new GuidePage(EN_US_GUIDE_PATH);
|
|
117
|
-
page.open();
|
|
118
|
-
await page.waitPageLoad();
|
|
119
|
-
|
|
120
|
-
const toolbar = await contentActionToolbar.get();
|
|
121
|
-
await contentActionToolbar.openMoreActionsMenu(toolbar);
|
|
122
|
-
|
|
123
|
-
const url = await contentActionToolbar.getUrlFromNewTab(() =>
|
|
124
|
-
contentActionToolbar.clickMenuOption(toolbar, 'View llms.txt'),
|
|
125
|
-
);
|
|
126
|
-
expect(url.endsWith(LLMS_TXT_PATH)).toBe(true);
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
it('should flash the Copied! tooltip when selecting Copy URL to Markdown', async () => {
|
|
130
|
-
const page = new GuidePage(EN_US_GUIDE_PATH);
|
|
131
|
-
page.open();
|
|
132
|
-
await page.waitPageLoad();
|
|
133
|
-
|
|
134
|
-
const toolbar = await contentActionToolbar.get();
|
|
135
|
-
await contentActionToolbar.openMoreActionsMenu(toolbar);
|
|
136
|
-
await contentActionToolbar.clickMenuOption(toolbar, 'Copy URL to Markdown');
|
|
137
|
-
|
|
138
|
-
await browser.waitUntil(
|
|
139
|
-
async () =>
|
|
140
|
-
(await contentActionToolbar.getVisibleCopiedTooltipLabel()) ===
|
|
141
|
-
COPIED_TOOLTIP_LABEL,
|
|
142
|
-
{
|
|
143
|
-
timeout: 5000,
|
|
144
|
-
timeoutMsg: 'Expected the menu tooltip to flash "Copied!"',
|
|
145
|
-
},
|
|
146
|
-
);
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
it('should not show the toolbar on a non-markdown reference', async () => {
|
|
44
|
+
it("Non-markdown reference shouldn't have toolbar", async () => {
|
|
150
45
|
const referencePage = new ReferencePage(NON_MARKDOWN_REFERENCE_PATH);
|
|
151
46
|
referencePage.open();
|
|
152
47
|
await referencePage.waitForSpecBasedReferencePageLoad();
|
|
153
48
|
|
|
154
|
-
|
|
49
|
+
const toolbar = await $('>>>doc-content-action-toolbar');
|
|
50
|
+
expect(await toolbar.isExisting()).toBeFalsy();
|
|
155
51
|
});
|
|
156
52
|
|
|
157
|
-
it(
|
|
53
|
+
it("Non en-us locale page shouldn't have toolbar", async () => {
|
|
158
54
|
const nonEnUsReferencePage = new ReferencePage(NON_EN_US_MARKDOWN_REFERENCE_PATH);
|
|
159
55
|
nonEnUsReferencePage.open();
|
|
160
56
|
await nonEnUsReferencePage.waitForMarkdownBasedReferencePageLoad();
|
|
161
|
-
|
|
162
|
-
expect(await contentActionToolbar.isPresent()).toBeFalsy();
|
|
57
|
+
expect(await (await $('>>>doc-content-action-toolbar')).isExisting()).toBeFalsy();
|
|
163
58
|
});
|
|
164
59
|
});
|
|
@@ -290,293 +290,6 @@ describe('Docs content type > locale picker', () => {
|
|
|
290
290
|
});
|
|
291
291
|
});
|
|
292
292
|
|
|
293
|
-
describe('Docs content type > localized (ja-jp) rendering', () => {
|
|
294
|
-
it('renders the Japanese body and sidebar on a ja-jp markdown topic', async () => {
|
|
295
|
-
page = new DocsPage('/docs/ja-jp/docs-content-type-example/get-started.html');
|
|
296
|
-
page.open();
|
|
297
|
-
await page.waitPageLoad();
|
|
298
|
-
await page.waitForSidebar();
|
|
299
|
-
|
|
300
|
-
expect(await (await page.getContentHeading()).getText()).toContain(
|
|
301
|
-
'ウィジェット サービスを始める',
|
|
302
|
-
);
|
|
303
|
-
// Sidebar tiles are localized — the Japanese "Widget Concepts" label resolves.
|
|
304
|
-
const tile = await page.getSidebarTile('ウィジェットの概念');
|
|
305
|
-
expect(await tile.isExisting()).toBe(true);
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
it('renders the Redoc view from a ja-jp spec URL', async () => {
|
|
309
|
-
page = new DocsPage('/docs/ja-jp/docs-content-type-example/sample-api');
|
|
310
|
-
page.open();
|
|
311
|
-
await page.waitForRedocLoad();
|
|
312
|
-
|
|
313
|
-
expect(await browser.getUrl()).toContain('/ja-jp/');
|
|
314
|
-
expect(await (await page.getRedocApiTitle()).getText()).not.toBe('');
|
|
315
|
-
});
|
|
316
|
-
});
|
|
317
|
-
|
|
318
|
-
describe('Docs content type > englishonly filter', () => {
|
|
319
|
-
it('serves the englishonly topic in English', async () => {
|
|
320
|
-
page = new DocsPage('/docs/docs-content-type-example/us-compliance-notes.html');
|
|
321
|
-
page.open();
|
|
322
|
-
await page.waitPageLoad();
|
|
323
|
-
|
|
324
|
-
expect(await (await page.getContentHeading()).getText()).toContain('US Compliance Notes');
|
|
325
|
-
});
|
|
326
|
-
});
|
|
327
|
-
|
|
328
|
-
describe('Docs content type > language picker round-trip', () => {
|
|
329
|
-
it('switches a markdown topic between English and Japanese and back', async () => {
|
|
330
|
-
page = new DocsPage('/docs/docs-content-type-example/get-started.html');
|
|
331
|
-
page.open();
|
|
332
|
-
await page.waitPageLoad();
|
|
333
|
-
await page.waitForSidebar();
|
|
334
|
-
|
|
335
|
-
await page.selectLanguage('日本語');
|
|
336
|
-
await page.waitPageLoad();
|
|
337
|
-
expect(await browser.getUrl()).toContain('/ja-jp/');
|
|
338
|
-
expect(await browser.getUrl()).toContain('get-started.html');
|
|
339
|
-
expect(await (await page.getContentHeading()).getText()).toContain(
|
|
340
|
-
'ウィジェット サービスを始める',
|
|
341
|
-
);
|
|
342
|
-
|
|
343
|
-
await page.selectLanguage('English');
|
|
344
|
-
await page.waitPageLoad();
|
|
345
|
-
expect(await browser.getUrl()).not.toContain('/ja-jp/');
|
|
346
|
-
expect(await browser.getUrl()).toContain('get-started.html');
|
|
347
|
-
expect(await (await page.getContentHeading()).getText()).toContain('Get Started');
|
|
348
|
-
});
|
|
349
|
-
});
|
|
350
|
-
|
|
351
|
-
describe('Docs content type > external link', () => {
|
|
352
|
-
it('exposes the real href and opens the external link in a new tab', async () => {
|
|
353
|
-
page = new DocsPage('/docs/docs-content-type-example/get-started.html');
|
|
354
|
-
page.open();
|
|
355
|
-
await page.waitPageLoad();
|
|
356
|
-
await page.waitForSidebar();
|
|
357
|
-
|
|
358
|
-
const tile = await page.getSidebarTile('Salesforce Developers');
|
|
359
|
-
const anchor = await tile.shadow$('a');
|
|
360
|
-
expect(await anchor.getAttribute('href')).toContain('https://developer.salesforce.com');
|
|
361
|
-
expect(await anchor.getAttribute('target')).toBe('_blank');
|
|
362
|
-
});
|
|
363
|
-
});
|
|
364
|
-
|
|
365
|
-
describe('Docs content type > Liquid templating', () => {
|
|
366
|
-
beforeEach(async () => {
|
|
367
|
-
page = new DocsPage('/docs/docs-content-type-example/liquid-widgets.html');
|
|
368
|
-
page.open();
|
|
369
|
-
await page.waitPageLoad();
|
|
370
|
-
});
|
|
371
|
-
|
|
372
|
-
it('resolves frontmatter assigns and liquid.data values in the body', async () => {
|
|
373
|
-
const content = await $('>>>doc-content-layout');
|
|
374
|
-
const text = await content.getText();
|
|
375
|
-
const html = await content.getHTML();
|
|
376
|
-
|
|
377
|
-
expect(text).toContain('Widget Automation');
|
|
378
|
-
expect(text).toContain('Hello from the docs liquid data file');
|
|
379
|
-
expect(html).not.toContain('{% assign');
|
|
380
|
-
expect(html).not.toContain('{{ feature }}');
|
|
381
|
-
expect(html).not.toContain('{{ data.tagline }}');
|
|
382
|
-
});
|
|
383
|
-
|
|
384
|
-
it('resolves repository-level variables from content-config.yml', async () => {
|
|
385
|
-
const content = await $('>>>doc-content-layout');
|
|
386
|
-
const text = await content.getText();
|
|
387
|
-
const html = await content.getHTML();
|
|
388
|
-
|
|
389
|
-
expect(text).toContain('Salesforce Platform');
|
|
390
|
-
expect(text).toContain('62.0');
|
|
391
|
-
expect(html).not.toContain('{{ variables.platform');
|
|
392
|
-
});
|
|
393
|
-
|
|
394
|
-
it('renders a loop over a liquid.data array', async () => {
|
|
395
|
-
const content = await $('>>>doc-content-layout');
|
|
396
|
-
const text = await content.getText();
|
|
397
|
-
const html = await content.getHTML();
|
|
398
|
-
|
|
399
|
-
expect(text).toContain('createWidget()');
|
|
400
|
-
expect(text).toContain('updateWidget()');
|
|
401
|
-
expect(text).toContain('deleteWidget()');
|
|
402
|
-
expect(html).not.toContain('for operation in data.operations');
|
|
403
|
-
});
|
|
404
|
-
|
|
405
|
-
it('resolves output expressions inside code blocks but keeps tags literal', async () => {
|
|
406
|
-
const codeBlockElement = await $('>>>.code-block-content');
|
|
407
|
-
const codeBlock = await codeBlockElement.shadow$('code');
|
|
408
|
-
|
|
409
|
-
expect(await codeBlock.isExisting()).toBeTruthy();
|
|
410
|
-
|
|
411
|
-
const codeText = await codeBlock.getText();
|
|
412
|
-
// {{ }} outputs resolve even inside code blocks.
|
|
413
|
-
expect(codeText).not.toContain('{{ data.tagline }}');
|
|
414
|
-
expect(codeText).toContain('Hello from the docs liquid data file');
|
|
415
|
-
expect(codeText).toContain('Widget Automation');
|
|
416
|
-
// {% %} tags stay literal inside code blocks.
|
|
417
|
-
expect(codeText).toContain('for op in ops');
|
|
418
|
-
});
|
|
419
|
-
|
|
420
|
-
it('renders a shared partial pulled in via include and its capture blocks', async () => {
|
|
421
|
-
const content = await $('>>>doc-content-layout');
|
|
422
|
-
const text = await content.getText();
|
|
423
|
-
const html = await content.getHTML();
|
|
424
|
-
|
|
425
|
-
expect(text).toContain('Reusable Tip');
|
|
426
|
-
expect(text).toContain('Widgets are created and managed through the Widgets API');
|
|
427
|
-
expect(text).toContain('Reusable Note');
|
|
428
|
-
expect(text).toContain('Every widget has a unique identifier');
|
|
429
|
-
// Dash-form tags are the fixture's real (resolved) tags; the bare "{% include %}"
|
|
430
|
-
// in the prose renders as literal inline code, so assert on the dash form only.
|
|
431
|
-
expect(html).not.toContain('{%- include');
|
|
432
|
-
expect(html).not.toContain('{%- capture');
|
|
433
|
-
expect(html).not.toContain('{{ liquid-widgets-reuse-tip }}');
|
|
434
|
-
});
|
|
435
|
-
});
|
|
436
|
-
|
|
437
|
-
describe('Docs content type > Liquid data object format', () => {
|
|
438
|
-
it('loads multiple data files each under a named key', async () => {
|
|
439
|
-
page = new DocsPage('/docs/docs-content-type-example/liquid-multi-data.html');
|
|
440
|
-
page.open();
|
|
441
|
-
await page.waitPageLoad();
|
|
442
|
-
|
|
443
|
-
const content = await $('>>>doc-content-layout');
|
|
444
|
-
const text = await content.getText();
|
|
445
|
-
const html = await content.getHTML();
|
|
446
|
-
|
|
447
|
-
expect(text).toContain('Hello from the docs liquid data file');
|
|
448
|
-
expect(text).toContain('Second data file loaded under a named key');
|
|
449
|
-
expect(html).not.toContain('{{ data.one.tagline }}');
|
|
450
|
-
expect(html).not.toContain('{{ two.summary }}');
|
|
451
|
-
});
|
|
452
|
-
});
|
|
453
|
-
|
|
454
|
-
describe('Docs content type > Liquid templating (ja-jp)', () => {
|
|
455
|
-
let jaLiquidEnabled = false;
|
|
456
|
-
const widgetsPath = '/docs/ja-jp/docs-content-type-example/liquid-widgets.html';
|
|
457
|
-
|
|
458
|
-
before(async () => {
|
|
459
|
-
try {
|
|
460
|
-
page = new DocsPage(widgetsPath);
|
|
461
|
-
page.open();
|
|
462
|
-
await page.waitPageLoad();
|
|
463
|
-
|
|
464
|
-
const content = await $('>>>doc-content-layout');
|
|
465
|
-
const text = await content.getText();
|
|
466
|
-
const html = await content.getHTML();
|
|
467
|
-
|
|
468
|
-
const hasLiquidOutput = text.includes(
|
|
469
|
-
'ドキュメント Liquid データ ファイルからこんにちは',
|
|
470
|
-
);
|
|
471
|
-
const hasRawSyntax = html.includes('{% assign') || html.includes('{{ data.tagline }}');
|
|
472
|
-
|
|
473
|
-
jaLiquidEnabled = hasLiquidOutput && !hasRawSyntax;
|
|
474
|
-
} catch (e) {
|
|
475
|
-
jaLiquidEnabled = false;
|
|
476
|
-
}
|
|
477
|
-
});
|
|
478
|
-
|
|
479
|
-
beforeEach(async () => {
|
|
480
|
-
if (jaLiquidEnabled) {
|
|
481
|
-
page = new DocsPage(widgetsPath);
|
|
482
|
-
page.open();
|
|
483
|
-
await page.waitPageLoad();
|
|
484
|
-
}
|
|
485
|
-
});
|
|
486
|
-
|
|
487
|
-
it('resolves frontmatter assigns and liquid.data values in a localized topic', async function () {
|
|
488
|
-
if (!jaLiquidEnabled) {
|
|
489
|
-
this.skip();
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
const content = await $('>>>doc-content-layout');
|
|
493
|
-
const text = await content.getText();
|
|
494
|
-
const html = await content.getHTML();
|
|
495
|
-
|
|
496
|
-
expect(text).toContain('ウィジェット オートメーション');
|
|
497
|
-
expect(text).toContain('ドキュメント Liquid データ ファイルからこんにちは');
|
|
498
|
-
expect(html).not.toContain('{% assign');
|
|
499
|
-
expect(html).not.toContain('{{ data.tagline }}');
|
|
500
|
-
});
|
|
501
|
-
|
|
502
|
-
it('resolves repository-level variables in a localized topic', async function () {
|
|
503
|
-
if (!jaLiquidEnabled) {
|
|
504
|
-
this.skip();
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
const content = await $('>>>doc-content-layout');
|
|
508
|
-
const text = await content.getText();
|
|
509
|
-
const html = await content.getHTML();
|
|
510
|
-
|
|
511
|
-
expect(text).toContain('Salesforce Platform');
|
|
512
|
-
expect(text).toContain('62.0');
|
|
513
|
-
expect(html).not.toContain('{{ variables.platform');
|
|
514
|
-
});
|
|
515
|
-
|
|
516
|
-
it('renders a loop and an included partial capture block in a localized topic', async function () {
|
|
517
|
-
if (!jaLiquidEnabled) {
|
|
518
|
-
this.skip();
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
const content = await $('>>>doc-content-layout');
|
|
522
|
-
const text = await content.getText();
|
|
523
|
-
const html = await content.getHTML();
|
|
524
|
-
|
|
525
|
-
expect(text).toContain('createWidget()');
|
|
526
|
-
expect(text).toContain('再利用可能なヒント');
|
|
527
|
-
expect(text).toContain('ウィジェットは Widgets API を通じて作成および管理されます');
|
|
528
|
-
expect(html).not.toContain('for operation in data.operations');
|
|
529
|
-
expect(html).not.toContain('{%- include');
|
|
530
|
-
expect(html).not.toContain('{%- capture');
|
|
531
|
-
});
|
|
532
|
-
});
|
|
533
|
-
|
|
534
|
-
describe('Docs content type > Liquid data object format (ja-jp)', () => {
|
|
535
|
-
let jaLiquidEnabled = false;
|
|
536
|
-
const multiDataPath = '/docs/ja-jp/docs-content-type-example/liquid-multi-data.html';
|
|
537
|
-
|
|
538
|
-
before(async () => {
|
|
539
|
-
try {
|
|
540
|
-
page = new DocsPage(multiDataPath);
|
|
541
|
-
page.open();
|
|
542
|
-
await page.waitPageLoad();
|
|
543
|
-
|
|
544
|
-
const content = await $('>>>doc-content-layout');
|
|
545
|
-
const text = await content.getText();
|
|
546
|
-
const html = await content.getHTML();
|
|
547
|
-
|
|
548
|
-
const hasLiquidOutput = text.includes(
|
|
549
|
-
'ドキュメント Liquid データ ファイルからこんにちは',
|
|
550
|
-
);
|
|
551
|
-
const hasRawSyntax =
|
|
552
|
-
html.includes('{{ data.one.tagline }}') || html.includes('{{ two.summary }}');
|
|
553
|
-
|
|
554
|
-
jaLiquidEnabled = hasLiquidOutput && !hasRawSyntax;
|
|
555
|
-
} catch (e) {
|
|
556
|
-
jaLiquidEnabled = false;
|
|
557
|
-
}
|
|
558
|
-
});
|
|
559
|
-
|
|
560
|
-
it('loads multiple data files each under a named key in a localized topic', async function () {
|
|
561
|
-
if (!jaLiquidEnabled) {
|
|
562
|
-
this.skip();
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
page = new DocsPage(multiDataPath);
|
|
566
|
-
page.open();
|
|
567
|
-
await page.waitPageLoad();
|
|
568
|
-
|
|
569
|
-
const content = await $('>>>doc-content-layout');
|
|
570
|
-
const text = await content.getText();
|
|
571
|
-
const html = await content.getHTML();
|
|
572
|
-
|
|
573
|
-
expect(text).toContain('ドキュメント Liquid データ ファイルからこんにちは');
|
|
574
|
-
expect(text).toContain('名前付きキーの下に読み込まれる 2 番目のデータ ファイル');
|
|
575
|
-
expect(html).not.toContain('{{ data.one.tagline }}');
|
|
576
|
-
expect(html).not.toContain('{{ two.summary }}');
|
|
577
|
-
});
|
|
578
|
-
});
|
|
579
|
-
|
|
580
293
|
describe('Docs content type > error routes', () => {
|
|
581
294
|
const expectNotFoundPage = async (): Promise<void> => {
|
|
582
295
|
const errorComponent = await $('dx-error');
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import DocsPage from '../pageobjects/docs.page';
|
|
2
1
|
import GuidePage from '../pageobjects/guide.page';
|
|
3
2
|
|
|
4
3
|
let page: GuidePage;
|
|
@@ -55,56 +54,3 @@ describe('Liquid Template Rendering — Advanced', () => {
|
|
|
55
54
|
expect(text.includes('previewFuture')).toBeTruthy();
|
|
56
55
|
});
|
|
57
56
|
});
|
|
58
|
-
|
|
59
|
-
describe('Docs content type > future filter', () => {
|
|
60
|
-
let docsFutureEnabled = false;
|
|
61
|
-
const overviewPath = '/docs/docs-content-type-example/get-started.html';
|
|
62
|
-
|
|
63
|
-
before(async () => {
|
|
64
|
-
try {
|
|
65
|
-
const docsPage = new DocsPage(overviewPath);
|
|
66
|
-
docsPage.open();
|
|
67
|
-
await docsPage.waitPageLoad();
|
|
68
|
-
|
|
69
|
-
const content = await $('>>>doc-content-layout');
|
|
70
|
-
const text = await content.getText();
|
|
71
|
-
|
|
72
|
-
docsFutureEnabled = text.includes(
|
|
73
|
-
'hidden in production and excluded from localization unless',
|
|
74
|
-
);
|
|
75
|
-
} catch (e) {
|
|
76
|
-
docsFutureEnabled = false;
|
|
77
|
-
}
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it('renders an inline future block when previewFuture is enabled', async function () {
|
|
81
|
-
if (!docsFutureEnabled) {
|
|
82
|
-
this.skip();
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const docsPage = new DocsPage(overviewPath);
|
|
86
|
-
docsPage.open();
|
|
87
|
-
await docsPage.waitPageLoad();
|
|
88
|
-
|
|
89
|
-
const content = await $('>>>doc-content-layout');
|
|
90
|
-
const text = await content.getText();
|
|
91
|
-
const html = await content.getHTML();
|
|
92
|
-
|
|
93
|
-
expect(text).toContain('hidden in production and excluded from localization unless');
|
|
94
|
-
expect(html).not.toContain('{% future');
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
it('renders the future topic page when previewFuture is enabled', async function () {
|
|
98
|
-
if (!docsFutureEnabled) {
|
|
99
|
-
this.skip();
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const docsPage = new DocsPage('/docs/docs-content-type-example/upcoming-graphql.html');
|
|
103
|
-
docsPage.open();
|
|
104
|
-
await docsPage.waitPageLoad();
|
|
105
|
-
|
|
106
|
-
expect(await (await docsPage.getContentHeading()).getText()).toContain(
|
|
107
|
-
'Upcoming: GraphQL API',
|
|
108
|
-
);
|
|
109
|
-
});
|
|
110
|
-
});
|
|
@@ -222,100 +222,3 @@ describe('Liquid Template Rendering', () => {
|
|
|
222
222
|
expect(text.includes('Liquid is a templating language supported in SFDocs')).toBeTruthy();
|
|
223
223
|
});
|
|
224
224
|
});
|
|
225
|
-
|
|
226
|
-
describe('Liquid Template Rendering — Localized (ja-jp)', () => {
|
|
227
|
-
let jaLiquidEnabled = false;
|
|
228
|
-
const quickstartPath = '/docs/ja-jp/liquid-project/guide/liquid-quickstart.html';
|
|
229
|
-
|
|
230
|
-
before(async () => {
|
|
231
|
-
try {
|
|
232
|
-
page = new GuidePage(quickstartPath);
|
|
233
|
-
await browser.url(page.path);
|
|
234
|
-
await page.waitPageLoad();
|
|
235
|
-
|
|
236
|
-
const content = await $('>>>doc-content-layout');
|
|
237
|
-
const text = await content.getText();
|
|
238
|
-
const html = await content.getHTML();
|
|
239
|
-
|
|
240
|
-
const hasLiquidOutput = text.includes('data.yaml からこんにちは');
|
|
241
|
-
const hasRawSyntax =
|
|
242
|
-
html.includes('{% assign topic') || html.includes('{{ data.greeting }}');
|
|
243
|
-
|
|
244
|
-
jaLiquidEnabled = hasLiquidOutput && !hasRawSyntax;
|
|
245
|
-
} catch (e) {
|
|
246
|
-
jaLiquidEnabled = false;
|
|
247
|
-
}
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
beforeEach(async () => {
|
|
251
|
-
if (jaLiquidEnabled) {
|
|
252
|
-
page = new GuidePage(quickstartPath);
|
|
253
|
-
await browser.url(page.path);
|
|
254
|
-
await page.waitPageLoad();
|
|
255
|
-
}
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
it('Should render frontmatter assigns and data.yaml values in Japanese', async function () {
|
|
259
|
-
if (!jaLiquidEnabled) {
|
|
260
|
-
this.skip();
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
const content = await $('>>>doc-content-layout');
|
|
264
|
-
const text = await content.getText();
|
|
265
|
-
|
|
266
|
-
expect(text.includes('ライターのための GitHub')).toBeTruthy();
|
|
267
|
-
expect(text.includes('data.yaml からこんにちは')).toBeTruthy();
|
|
268
|
-
});
|
|
269
|
-
|
|
270
|
-
it('Should render repository-level variables in a localized topic', async function () {
|
|
271
|
-
if (!jaLiquidEnabled) {
|
|
272
|
-
this.skip();
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
const content = await $('>>>doc-content-layout');
|
|
276
|
-
const text = await content.getText();
|
|
277
|
-
const html = await content.getHTML();
|
|
278
|
-
|
|
279
|
-
expect(text.includes('Salesforce Platform')).toBeTruthy();
|
|
280
|
-
expect(text.includes('62.0')).toBeTruthy();
|
|
281
|
-
expect(html.includes('{{ variables.platform')).toBeFalsy();
|
|
282
|
-
});
|
|
283
|
-
|
|
284
|
-
it('Should render a loop and an included partial capture block in Japanese', async function () {
|
|
285
|
-
if (!jaLiquidEnabled) {
|
|
286
|
-
this.skip();
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
const content = await $('>>>doc-content-layout');
|
|
290
|
-
const text = await content.getText();
|
|
291
|
-
const html = await content.getHTML();
|
|
292
|
-
|
|
293
|
-
expect(text.includes('create()')).toBeTruthy();
|
|
294
|
-
expect(text.includes('再利用可能なヒント')).toBeTruthy();
|
|
295
|
-
expect(
|
|
296
|
-
text.includes('ウィジェットは Widgets API を通じて作成および管理されます'),
|
|
297
|
-
).toBeTruthy();
|
|
298
|
-
// Dash-form tags are the fixture's real (resolved) tags; the bare "{% include %}"
|
|
299
|
-
// in the prose renders as literal inline code, so assert on the dash form only.
|
|
300
|
-
expect(html.includes('{%- include')).toBeFalsy();
|
|
301
|
-
expect(html.includes('{%- capture')).toBeFalsy();
|
|
302
|
-
});
|
|
303
|
-
|
|
304
|
-
it('Should render liquid.data object format in a localized topic', async function () {
|
|
305
|
-
if (!jaLiquidEnabled) {
|
|
306
|
-
this.skip();
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
const multiDataPage = new GuidePage('/docs/ja-jp/liquid-project/guide/multi-data.html');
|
|
310
|
-
await browser.url(multiDataPage.path);
|
|
311
|
-
await multiDataPage.waitPageLoad();
|
|
312
|
-
|
|
313
|
-
const content = await $('>>>doc-content-layout');
|
|
314
|
-
const text = await content.getText();
|
|
315
|
-
const html = await content.getHTML();
|
|
316
|
-
|
|
317
|
-
expect(text.includes('小売店舗や顧客サイト')).toBeTruthy();
|
|
318
|
-
expect(html.includes('{{ data.one.summary }}')).toBeFalsy();
|
|
319
|
-
expect(html.includes('{{ two.summary }}')).toBeFalsy();
|
|
320
|
-
});
|
|
321
|
-
});
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
class ContentActionToolbar {
|
|
2
|
-
HOST_SELECTOR = 'doc-content-action-toolbar';
|
|
3
|
-
INLINE_BUTTON_SELECTOR = 'dx-button.toolbar-button';
|
|
4
|
-
MENU_TRIGGER_SELECTOR = 'dx-button.menu-trigger';
|
|
5
|
-
MENU_SELECTOR = 'dx-dropdown.toolbar-menu';
|
|
6
|
-
MENU_GROUP_SELECTOR = 'div.menu_option';
|
|
7
|
-
MENU_HEADING_SELECTOR = 'span.menu_heading';
|
|
8
|
-
MENU_OPTION_SELECTOR = 'dx-dropdown-option';
|
|
9
|
-
OPTION_LABEL_SELECTOR = 'div.option_label';
|
|
10
|
-
COPIED_TOOLTIP_SELECTOR = '[role="tooltip"].is-visible';
|
|
11
|
-
|
|
12
|
-
async get(): Promise<WebdriverIO.Element> {
|
|
13
|
-
const toolbar = await $(`>>>${this.HOST_SELECTOR}`);
|
|
14
|
-
await toolbar.waitForDisplayed();
|
|
15
|
-
return toolbar;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
async isPresent(): Promise<boolean> {
|
|
19
|
-
return (await $(`>>>${this.HOST_SELECTOR}`)).isExisting();
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
async getInlineButtonLabels(toolbar: WebdriverIO.Element): Promise<string[]> {
|
|
23
|
-
const inlineButtons = await toolbar.shadow$$(this.INLINE_BUTTON_SELECTOR);
|
|
24
|
-
return Promise.all(inlineButtons.map(async (button) => (await button.getText()).trim()));
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
async openMoreActionsMenu(toolbar: WebdriverIO.Element): Promise<void> {
|
|
28
|
-
const trigger = await toolbar.shadow$(this.MENU_TRIGGER_SELECTOR);
|
|
29
|
-
await trigger.waitForClickable();
|
|
30
|
-
await trigger.click();
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
async getMenuOptionLabelsByGroup(
|
|
34
|
-
toolbar: WebdriverIO.Element,
|
|
35
|
-
): Promise<Record<string, string[]>> {
|
|
36
|
-
const dropdown = await toolbar.shadow$(this.MENU_SELECTOR);
|
|
37
|
-
const groupContainers = await dropdown.shadow$$(this.MENU_GROUP_SELECTOR);
|
|
38
|
-
|
|
39
|
-
const labelsByGroup: Record<string, string[]> = {};
|
|
40
|
-
for (const group of groupContainers) {
|
|
41
|
-
const heading = (await (await group.$(this.MENU_HEADING_SELECTOR)).getText()).trim();
|
|
42
|
-
const options = await group.$$(this.MENU_OPTION_SELECTOR);
|
|
43
|
-
labelsByGroup[heading] = await Promise.all(
|
|
44
|
-
options.map(async (option) =>
|
|
45
|
-
(await (await option.shadow$(this.OPTION_LABEL_SELECTOR)).getText()).trim(),
|
|
46
|
-
),
|
|
47
|
-
);
|
|
48
|
-
}
|
|
49
|
-
return labelsByGroup;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
async clickMenuOption(toolbar: WebdriverIO.Element, label: string): Promise<void> {
|
|
53
|
-
const dropdown = await toolbar.shadow$(this.MENU_SELECTOR);
|
|
54
|
-
const options = await dropdown.shadow$$(this.MENU_OPTION_SELECTOR);
|
|
55
|
-
for (const option of options) {
|
|
56
|
-
const optionLabel = (
|
|
57
|
-
await (await option.shadow$(this.OPTION_LABEL_SELECTOR)).getText()
|
|
58
|
-
).trim();
|
|
59
|
-
if (optionLabel === label) {
|
|
60
|
-
await option.click();
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
throw new Error(`Menu option "${label}" not found`);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
async getUrlFromNewTab(action: () => Promise<void>): Promise<string> {
|
|
68
|
-
const originalHandle = await browser.getWindowHandle();
|
|
69
|
-
const handlesBefore = await browser.getWindowHandles();
|
|
70
|
-
|
|
71
|
-
await action();
|
|
72
|
-
|
|
73
|
-
await browser.waitUntil(
|
|
74
|
-
async (): Promise<boolean> =>
|
|
75
|
-
(await browser.getWindowHandles()).length > handlesBefore.length,
|
|
76
|
-
{
|
|
77
|
-
timeout: 10000,
|
|
78
|
-
timeoutMsg: 'Expected a new browser tab to open',
|
|
79
|
-
},
|
|
80
|
-
);
|
|
81
|
-
|
|
82
|
-
const handlesAfter = await browser.getWindowHandles();
|
|
83
|
-
const newHandle = handlesAfter.find((handle) => !handlesBefore.includes(handle));
|
|
84
|
-
if (!newHandle) {
|
|
85
|
-
throw new Error('New browser tab opened but its handle could not be resolved');
|
|
86
|
-
}
|
|
87
|
-
await browser.switchToWindow(newHandle);
|
|
88
|
-
const url = await browser.getUrl();
|
|
89
|
-
|
|
90
|
-
await browser.closeWindow();
|
|
91
|
-
await browser.switchToWindow(originalHandle);
|
|
92
|
-
return url;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
async getVisibleCopiedTooltipLabel(): Promise<string> {
|
|
96
|
-
const tooltip = await $(`>>>${this.COPIED_TOOLTIP_SELECTOR}`);
|
|
97
|
-
return (await tooltip.getAttribute('aria-label'))?.trim() ?? '';
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
export default new ContentActionToolbar();
|