1   package com.atlassian.plugins.codegen.modules.confluence.blueprint;
2   
3   import com.atlassian.plugins.codegen.AbstractModuleCreatorTestCase;
4   import com.atlassian.plugins.codegen.ClassId;
5   import com.atlassian.plugins.codegen.ComponentDeclaration;
6   import com.atlassian.plugins.codegen.PluginProjectChangeset;
7   import com.atlassian.plugins.codegen.ResourceFile;
8   import com.atlassian.plugins.codegen.SourceFile;
9   import com.atlassian.plugins.codegen.modules.AbstractNameBasedModuleProperties;
10  import com.atlassian.plugins.codegen.modules.common.web.WebResourceProperties;
11  import com.google.common.collect.Lists;
12  import org.dom4j.Document;
13  import org.dom4j.Element;
14  import org.dom4j.Node;
15  import org.junit.After;
16  import org.junit.Before;
17  import org.junit.Test;
18  
19  import java.util.List;
20  
21  import static com.atlassian.plugins.codegen.modules.confluence.blueprint.BlueprintI18nProperty.*;
22  import static com.atlassian.plugins.codegen.modules.confluence.blueprint.BlueprintPromptEntry.*;
23  import static java.lang.String.format;
24  import static org.hamcrest.Matchers.*;
25  import static org.junit.Assert.*;
26  
27  /**
28   * Tests that the {@link BlueprintModuleCreator}, given a valid set of {@link BlueprintProperties}, creates the correct
29   * plugin modules and files.
30   *
31   * NOTE - this test uses the {@link BlueprintBuilder} to generate the {@link BlueprintProperties} passed to the
32   * {@link BlueprintModuleCreator}, which makes this test somewhat of an Integration test. If tests in this class start
33   * failing, check for unit-test failures in the BlueprintBuilderTest.
34   *
35   * @since 4.1.8
36   */
37  public class BlueprintModuleCreatorTest extends AbstractModuleCreatorTestCase<BlueprintProperties>
38  {
39      public static final String PLUGIN_KEY = "com.atlassian.confluence.plugins.foo-print";
40      /**
41       *  We use the builder to turn simple prompt properties into complex BlueprintProperties objects and reduce
42       *  duplication in the test. This means that we assume the Builder does its job correctly - this is the
43       *  responsibility of the BlueprintBuilder unit test.
44       */
45      private BlueprintPromptEntries promptProps;
46  
47      private BlueprintProperties blueprintProps;
48  
49      // The expected values for the Properties and created plugin content
50      private String blueprintModuleKey = "foo-print-blueprint";
51      private String blueprintIndexKey = "foo-print";
52      private String webItemName = "FooPrint";
53      private String webItemDesc = "There's no Blueprint like my FooPrint.";
54      private String templateModuleKey = "foo-plate";
55  
56      public BlueprintModuleCreatorTest()
57      {
58          super("blueprint", new BlueprintModuleCreator());
59      }
60  
61      @Before
62      public void setupBaseProps() throws Exception
63      {
64          createBasePromptProperties();
65          buildBlueprintProperties();
66  
67      }
68  
69      private void buildBlueprintProperties()
70      {
71          // The inherited tests assume that we have a BlueprintProperties in setup, so the object has
72          // to exist before our tests can update the promptProps and build a new one. The workaround is to recreate
73          // the properties in the tests that need them.
74          changeset = null;
75          blueprintProps = new BlueprintBuilder(promptProps).build();
76          setProps(blueprintProps);
77      }
78  
79      /**
80       * Common properties for all tests - creates the most basic combination of blueprint, content-template and
81       * web-item elements / resources that will result in a browser-testable Blueprint.
82       */
83      private void createBasePromptProperties()
84      {
85          promptProps = new BlueprintPromptEntries(PLUGIN_KEY);
86  
87          promptProps.put(INDEX_KEY_PROMPT, blueprintIndexKey);
88          promptProps.put(WEB_ITEM_NAME_PROMPT, webItemName);
89          promptProps.put(WEB_ITEM_DESC_PROMPT, webItemDesc);
90  
91          List<String> contentTemplateKeys = Lists.newArrayList();
92          contentTemplateKeys.add(templateModuleKey);
93          promptProps.put(CONTENT_TEMPLATE_KEYS_PROMPT, contentTemplateKeys);
94      }
95  
96      @After
97      public void tearDown()
98      {
99          changeset = null;
100     }
101 
102     @Test
103     public void blueprintModuleBasicSettings() throws Exception
104     {
105         Element blueprintModule = getGeneratedModule();
106         assertNodeText(blueprintModule, "@key", blueprintModuleKey);
107         String blueprintModuleName = "FooPrint Blueprint";
108         assertNodeText(blueprintModule, "@name", blueprintModuleName);
109         assertNodeText(blueprintModule, "@index-key", blueprintIndexKey);
110         assertNodeText(blueprintModule, "content-template/@ref", templateModuleKey);
111 
112         String indexPageI18nTitle = PLUGIN_KEY + ".index.page.title";
113         assertNodeText(blueprintModule, "@i18n-index-title-key", indexPageI18nTitle);
114 
115         assertI18nString(indexPageI18nTitle, "FooPrints");
116     }
117 
118     @Test
119     public void contentTemplateModuleBasicSettings() throws Exception
120     {
121         Element templateModule = getGeneratedModule("content-template");
122         assertNodeText(templateModule, "@key", templateModuleKey);
123         assertNodeText(templateModule, "@i18n-name-key", PLUGIN_KEY + "." + "foo-plate.name");
124         assertNodeText(templateModule, "description/@key", PLUGIN_KEY + "." + "foo-plate.desc");
125 
126         assertNodeText(templateModule, "resource/@name", "template");
127         assertNodeText(templateModule, "resource/@type", "download");
128         assertNodeText(templateModule, "resource/@location", "xml/" + templateModuleKey + ".xml");
129 
130         assertI18nString(PLUGIN_KEY + "." + templateModuleKey + ".name", "FooPrint Content Template 0");
131         String templateDesc = "Contains Storage-format XML used by the FooPrint Blueprint";
132         assertI18nString(PLUGIN_KEY + "." + templateModuleKey + ".desc", templateDesc);
133     }
134 
135     @Test
136     public void contentTemplateFileIsCreated() throws Exception
137     {
138         ResourceFile file = getResourceFile("xml", templateModuleKey + ".xml");
139         String xml = new String(file.getContent());
140         String templateContentI18nKey = PLUGIN_KEY + "." + templateModuleKey + ".content.text";
141         assertThat(xml, containsString(templateContentI18nKey));
142         assertThat(xml, containsString("ac:placeholder"));
143         assertThat(xml, containsString("ac:type=\"mention\""));
144         assertI18nString(templateContentI18nKey, ContentTemplateProperties.CONTENT_I18N_DEFAULT_VALUE);
145     }
146 
147     @Test
148     public void indexPageTemplateFileIsCreated() throws Exception
149     {
150         promptProps.put(INDEX_PAGE_TEMPLATE_PROMPT, true);
151         buildBlueprintProperties();
152 
153         Element blueprintModule = getGeneratedModule();
154         String templateModuleKey = BlueprintProperties.INDEX_TEMPLATE_DEFAULT_KEY;
155         assertNodeText(blueprintModule, "@index-template-key", templateModuleKey);
156 
157         Document templateModules = getAllGeneratedModulesOfType("content-template");
158         Element templateModule = (Element)templateModules.selectSingleNode("//content-template[@key='custom-index-page-template']");
159         assertNodeText(templateModule, "@key", templateModuleKey);
160         assertNodeText(templateModule, "@i18n-name-key", "com.atlassian.confluence.plugins.foo-print.custom-index-page-template.name");
161         assertNodeText(templateModule, "description/@key", "com.atlassian.confluence.plugins.foo-print.custom-index-page-template.desc");
162 
163         assertNodeText(templateModule, "resource/@name", "template");
164         assertNodeText(templateModule, "resource/@type", "download");
165         assertNodeText(templateModule, "resource/@location", "xml/" + templateModuleKey + ".xml");
166 
167         assertI18nString("com.atlassian.confluence.plugins.foo-print.custom-index-page-template.name", "Custom Index Page Content Template");
168         String templateDesc = "Contains Storage-format XML used by the FooPrint Blueprint's Index page";
169         assertI18nString("com.atlassian.confluence.plugins.foo-print.custom-index-page-template.desc", templateDesc);
170 
171         ResourceFile file = getResourceFile("xml", templateModuleKey + ".xml");
172         String xml = new String(file.getContent());
173         String templateContentI18nKey = PLUGIN_KEY + "." + templateModuleKey + ".content.text";
174         assertThat(xml, containsString(templateContentI18nKey));
175         assertI18nString(templateContentI18nKey, ContentTemplateProperties.INDEX_TEMPLATE_CONTENT_VALUE);
176     }
177 
178     @Test
179     public void contextProviderIsAddedToContentTemplate() throws Exception
180     {
181         promptProps.put(CONTEXT_PROVIDER_PROMPT, true);
182         buildBlueprintProperties();
183 
184         // 1. Context provider is added to content-template element
185         Element templateModule = getGeneratedModule("content-template");
186         String className = "ContentTemplateContextProvider";
187         String packageName = "com.atlassian.confluence.plugins.foo_print";  // foo-print without the '-'
188         String fqClassName = packageName + "." + className;
189         assertNodeText(templateModule, "context-provider/@class", fqClassName);
190 
191         // 2. ContextProviderProperties Java class is added.
192         SourceFile sourceFile = getSourceFile(packageName, className);
193         assertThat(sourceFile.getContent(), containsString("package " + packageName + ";"));
194         assertThat(sourceFile.getContent(), containsString("\"variableA\""));
195         assertThat(sourceFile.getContent(), containsString("\"variableB\""));
196 
197         // 3. Context added by provider is referenced in the template.
198         ResourceFile file = getResourceFile("xml", templateModuleKey + ".xml");
199         String xml = new String(file.getContent());
200         assertThat(xml, containsString("<at:var at:name=\"variableA\" />"));
201         assertThat(xml, containsString("<at:var at:name=\"variableB\" at:rawXhtml=\"true\" />"));
202     }
203 
204     @Test
205     public void webItemModuleBasicSettings() throws Exception
206     {
207         String webItemNameI18nKey = PLUGIN_KEY + ".blueprint.display.name";
208         String webItemDescI18nKey = PLUGIN_KEY + ".blueprint.display.desc";
209 
210         Element module = getGeneratedModule("web-item");
211         assertNameBasedModuleProperties(module, blueprintProps.getWebItem());
212 
213         assertNodeText(module, "@key", "foo-print-blueprint-web-item");
214         assertNodeText(module, "@i18n-name-key", webItemNameI18nKey);
215         assertNodeText(module, "@section", "system.create.dialog/content");
216         assertNodeText(module, "description/@key", webItemDescI18nKey);
217         assertNodeText(module, "param/@name", BlueprintProperties.WEB_ITEM_BLUEPRINT_KEY);
218         assertNodeText(module, "param/@value", blueprintModuleKey);
219 
220         assertI18nString(webItemNameI18nKey, webItemName);
221         assertI18nString(webItemDescI18nKey, webItemDesc);
222     }
223 
224     @Test
225     public void webItemIconIsCreated() throws Exception
226     {
227         Element webItem = getGeneratedModule("web-item");
228         assertNodeText(webItem, "styleClass", "icon-foo-print-blueprint large");
229 
230         Element webResource = getGeneratedModule("web-resource");
231         assertNodeText(webResource, "resource[@name='blueprints.css']/@type", "download");
232         assertNodeText(webResource, "resource[@name='blueprints.css']/@location", "css/blueprints.css");
233 
234         String css = new String(getResourceFile("css", "blueprints.css").getContent());
235         assertThat(css, containsString(".icon-foo-print-blueprint.large {"));
236         assertThat(css, containsString(".acs-nav-item.blueprint.foo-print .icon {"));
237     }
238 
239     @Test
240     public void howToUseTemplateIsAdded() throws Exception
241     {
242         promptProps.put(HOW_TO_USE_PROMPT, true);
243         buildBlueprintProperties();
244 
245         // 1. The blueprint element should have a new attribute with the how-to-use template reference
246         Element blueprintModule = getGeneratedModule();
247         assertNodeText(blueprintModule, "@how-to-use-template", blueprintProps.getHowToUseTemplate());
248 
249         // 2. There should be a Soy file containing the referenced template
250         String soyHeadingI18nKey = PLUGIN_KEY + ".wizard.how-to-use.heading";
251         String soyContentI18nKey = PLUGIN_KEY + ".wizard.how-to-use.content";
252         String soy = new String(getResourceFile("soy", "my-templates.soy").getContent());
253         assertThat(soy, containsString(format("{namespace %s}", "Confluence.Blueprints.Plugin.FooPrint")));
254         assertThat(soy, containsString("{template .howToUse}"));
255         assertThat(soy, containsString(format("{getText('%s')}", soyHeadingI18nKey)));
256         assertThat(soy, containsString(format("{getText('%s')}", soyContentI18nKey)));
257 
258         // 3. There should be a web-resource pointing to the new file
259         Element webResourceModule = getGeneratedModule("web-resource");
260         assertWebResource(webResourceModule, blueprintProps.getWebResource());
261 
262         // 4. There should new entries in the i18n file for the template
263         assertI18nString(soyHeadingI18nKey, HOW_TO_USE_HEADING.getI18nValue());
264         assertI18nString(soyContentI18nKey, HOW_TO_USE_CONTENT.getI18nValue());
265     }
266 
267     @Test
268     public void editorIsSkipped() throws Exception
269     {
270         promptProps.put(SKIP_PAGE_EDITOR_PROMPT, true);
271         buildBlueprintProperties();
272 
273         Element blueprintModule = getGeneratedModule();
274         assertNodeText(blueprintModule, "@create-result", BlueprintProperties.CREATE_RESULT_VIEW);
275 
276         // If the edior is skipped there should be no placeholders in the generated template XML file
277         String xml = new String(getResourceFile("xml", templateModuleKey + ".xml").getContent());
278         assertThat(xml, not(containsString("ac:placeholder")));
279     }
280 
281     @Test
282     public void dialogWizardIsAdded() throws Exception
283     {
284         promptProps.put(DIALOG_WIZARD_PROMPT, true);
285         buildBlueprintProperties();
286 
287         // 1. The blueprint element should have a new dialog-wizard element and children
288         Element blueprintModule = getGeneratedModule();
289         Element wizardElement = blueprintModule.element("dialog-wizard");
290         assertNotNull("dialog-wizard element should be created", wizardElement);
291         assertNodeText(wizardElement, "@key", "foo-print-wizard");
292 
293         Element pageElement = wizardElement.element("dialog-page");
294         assertNotNull("dialog-page element should be created", pageElement);
295         assertNodeText(pageElement, "@id", "page0");
296         assertNodeText(pageElement, "@template-key", "Confluence.Blueprints.Plugin.FooPrint.wizardPage0");
297 
298         String wizardPageTitle = PLUGIN_KEY + ".wizard.page0.title";
299         String wizardPageDescHeader = PLUGIN_KEY + ".wizard.page0.desc.header";
300         String wizardPageDescContent = PLUGIN_KEY + ".wizard.page0.desc.content";
301         assertNodeText(pageElement, "@title-key", wizardPageTitle);
302         assertNodeText(pageElement, "@description-header-key", wizardPageDescHeader);
303         assertNodeText(pageElement, "@description-content-key", wizardPageDescContent);
304         assertI18nString(wizardPageTitle, "Wizard Page 0 Title");
305         assertI18nString(wizardPageDescHeader, "Page 0 Description");
306         assertI18nString(wizardPageDescContent, "This wizard page does A, B and C");
307 
308         // 2. There should be a Soy file containing the referenced template
309         String soy = new String(getResourceFile("soy", "my-templates.soy").getContent());
310         String fieldId = "foo-print-blueprint-page-title";
311         assertThat(soy, containsString(format("{namespace %s}", "Confluence.Blueprints.Plugin.FooPrint")));
312         assertThat(soy, containsString("{template .wizardPage0}"));
313         assertThat(soy, containsString(fieldId));
314 
315         // 3. There should be a JS file containing wizard callbacks
316         String js = new String(getResourceFile("js", "dialog-wizard.js").getContent());
317         assertThat(js, containsString("setWizard('" + PLUGIN_KEY + ":foo-print-blueprint-web-item"));
318         assertThat(js, containsString(fieldId));
319         
320         // 4. There should be a web-resource pointing to the new files
321         Element webResourceModule = getGeneratedModule("web-resource");
322         assertWebResource(webResourceModule, blueprintProps.getWebResource());
323 
324         // 5. There should be new entries in the i18n file for the JS, and matching references in the Soy and JS files
325         String titleLabel = PLUGIN_KEY + ".wizard.page0.title.label";
326         String titlePlace = PLUGIN_KEY + ".wizard.page0.title.placeholder";
327         String titleError = PLUGIN_KEY + ".wizard.page0.title.error";
328         String preRender  = PLUGIN_KEY + ".wizard.page0.pre-render";
329         String postRender = PLUGIN_KEY + ".wizard.page0.post-render";
330 
331         assertThat(soy, containsString(format("{getText('%s')}", titleLabel)));
332         assertThat(soy, containsString(format("{getText('%s')}", titlePlace)));
333         assertThat(js, containsString(format("AJS.I18n.getText('%s')", preRender)));
334         assertThat(js, containsString(format("AJS.I18n.getText('%s')", postRender)));
335         assertThat(js, containsString(format("AJS.I18n.getText('%s')", titleError)));
336 
337         // 6. There should be an at:var for the wizardVariable field in the wizard
338         String xml = new String(getResourceFile("xml", templateModuleKey + ".xml").getContent());
339         assertThat(xml, containsString("wizardVariable"));
340 
341         // The combination of Velocity, JavaScript and jQuery can lead to problems if variable names start with "$".
342         // Using the correct escape characters in Velocity should fix this - test that the rendered JS is correct.
343         assertThat(js, containsString("state.$container;"));
344         assertThat(js, not(containsString("(.trim")));
345         assertThat(js, not(containsString("\\$")));
346         assertThat(js, not(containsString("${")));
347 
348         assertI18nString(titleLabel, WIZARD_FORM_TITLE_FIELD_LABEL.getI18nValue());
349         assertI18nString(titlePlace, WIZARD_FORM_TITLE_FIELD_PLACEHOLDER.getI18nValue());
350         assertI18nString(preRender, WIZARD_FORM_PRE_RENDER_TEXT.getI18nValue());
351         assertI18nString(postRender, WIZARD_FORM_POST_RENDER_TEXT.getI18nValue());
352         assertI18nString(titleError, WIZARD_FORM_TITLE_FIELD_ERROR.getI18nValue());
353     }
354 
355     @Test
356     public void eventListenerIsAdded() throws Exception
357     {
358         promptProps.put(EVENT_LISTENER_PROMPT, true);
359         buildBlueprintProperties();
360 
361         // 1. Listener Component should be added to plugin XML
362         String packageName = "com.atlassian.confluence.plugins.foo_print";
363         String className = "BlueprintCreatedListener";
364         ClassId classId = ClassId.packageAndClass(packageName, className);
365         ComponentDeclaration component = getComponentOfClass(classId);
366         assertThat(component.getKey(), is("blueprint-created-listener"));
367         assertThat(component.getName().get(), is("Blueprint Created Event Listener"));
368 
369         // 2. Listener class should be added
370         SourceFile sourceFile = getSourceFile(packageName, className);
371         assertThat(sourceFile.getContent(), containsString("package " + packageName + ";"));
372         assertThat(sourceFile.getContent(), containsString(PLUGIN_KEY));
373     }
374 
375     private void assertWebResource(Element element, WebResourceProperties expectedResource)
376     {
377         assertNameBasedModuleProperties(element, expectedResource);
378 
379         assertNodeText(element, "context[1]", expectedResource.getContexts().get(0));
380         assertNodeText(element, "context[2]", expectedResource.getContexts().get(1));
381 
382         assertNodeText(element, "dependency", expectedResource.getDependencies().get(0));
383 
384         assertNotNull(element.selectSingleNode("transformation"));
385         assertNodeText(element, "transformation[@extension='soy']/transformer/@key", "soyTransformer");
386         assertNodeText(element, "transformation[@extension='soy']/transformer/functions",
387             "com.atlassian.confluence.plugins.soy:soy-core-functions");
388 
389         // Only check for JS if added to the expected web-resource
390         if (expectedResource.getTransformations().size() == 2)
391         {
392             assertNodeText(element, "transformation[@extension='js']/transformer/@key", "jsI18n");
393         }
394     }
395 
396     private void assertNameBasedModuleProperties(Element element, AbstractNameBasedModuleProperties props)
397     {
398         assertNodeText(element, "@key", props.getModuleKey());
399         assertNodeText(element, "@name", props.getModuleName());
400         assertNodeText(element, "@i18n-name-key", props.getNameI18nKey());
401         assertNodeText(element, "description/@key", props.getDescriptionI18nKey());
402         assertNodeText(element, "description", props.getDescription());
403     }
404 
405     // Cache the changeset during each test.
406     @Override
407     protected PluginProjectChangeset getChangesetForModule() throws Exception
408     {
409         if (changeset == null)
410         {
411             changeset = super.getChangesetForModule();
412         }
413         return changeset;
414     }
415 
416     private void assertNodeText(Element element, String nodePath, String expectedText)
417     {
418         assertEquals(expectedText, getText(element, nodePath));
419     }
420 
421     private String getText(Element element, String nodePath)
422     {
423         Node node = element.selectSingleNode(nodePath);
424         assertNotNull("Couldn't find node with path: " + nodePath, node);
425         return node.getText();
426     }
427 
428     // I prefer this method name - getI18nString doesn't indicate that an Assert is being done. dT
429     private void assertI18nString(String i18nKey, String value) throws Exception
430     {
431         getI18nString(i18nKey, value);
432     }
433 }