From 57a5aa0f4ba69707280fc2a2fa7765914d73c43d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emil=20Stenstr=C3=B6m?= Date: Wed, 24 Mar 2021 22:47:48 +0100 Subject: [PATCH] Only render dependencies that are used of a specific page (#52) Co-authored-by: rbeard0330 <@dul2k3BKW6m> --- benchmarks/component_rendering.py | 112 +- django_components/component.py | 4 +- django_components/middleware.py | 74 + .../templatetags/component_tags.py | 52 +- tests/django_test_setup.py | 2 + tests/templates/mdn_complete_page.html | 2101 +++++++++++++++++ tests/templates/mdn_component_template.html | 14 + tests/test_context.py | 11 +- tests/test_dependency_rendering.py | 202 ++ tests/test_templatetags.py | 65 - tests/testutils.py | 38 +- 11 files changed, 2576 insertions(+), 99 deletions(-) create mode 100644 django_components/middleware.py create mode 100644 tests/templates/mdn_complete_page.html create mode 100644 tests/templates/mdn_component_template.html create mode 100644 tests/test_dependency_rendering.py diff --git a/benchmarks/component_rendering.py b/benchmarks/component_rendering.py index 505a7da7..bc24c1f4 100644 --- a/benchmarks/component_rendering.py +++ b/benchmarks/component_rendering.py @@ -1,10 +1,12 @@ from time import perf_counter from django.template import Context, Template +from django.test import override_settings -from django_components import component +from django_components.middleware import CSS_DEPENDENCY_PLACEHOLDER, JS_DEPENDENCY_PLACEHOLDER from tests.django_test_setup import * # NOQA -from tests.testutils import Django30CompatibleSimpleTestCase as SimpleTestCase +from django_components import component +from tests.testutils import Django30CompatibleSimpleTestCase as SimpleTestCase, create_and_process_template_response class SlottedComponent(component.Component): @@ -22,20 +24,112 @@ class SimpleComponent(component.Component): def template(self, context): return "simple_template.html" + class Media: + css = {"all": ["style.css"]} + js = ["script.js"] + +class BreadcrumbComponent(component.Component): + LINKS = [ + ('https://developer.mozilla.org/en-US/docs/Learn', + 'Learn web development'), + ('https://developer.mozilla.org/en-US/docs/Learn/HTML', + 'Structuring the web with HTML'), + ('https://developer.mozilla.org/en-US/docs/Learn/HTML/Introduction_to_HTML', + 'Introduction to HTML'), + ('https://developer.mozilla.org/en-US/docs/Learn/HTML/Introduction_to_HTML/Document_and_website_structure', + 'Document and website structure') + ] + + def context(self, items): + if items > 4: + items = 4 + elif items < 0: + items = 0 + return {'links': self.LINKS[:items - 1]} + + def template(self, context): + return "mdn_component_template.html" + + class Media: + css = {"all": ["test.css"]} + js = ["test.js"] + + +EXPECTED_CSS = """""" +EXPECTED_JS = """""" + + +@override_settings(COMPONENTS={'RENDER_DEPENDENCIES': True}) class RenderBenchmarks(SimpleTestCase): def setUp(self): component.registry.clear() component.registry.register('test_component', SlottedComponent) component.registry.register('inner_component', SimpleComponent) + component.registry.register('breadcrumb_component', BreadcrumbComponent) - def test_render_time(self): + @staticmethod + def timed_loop(func, iterations=1000): + """Run func iterations times, and return the time in ms per iteration.""" + start_time = perf_counter() + for _ in range(iterations): + func() + end_time = perf_counter() + total_elapsed = end_time - start_time # NOQA + return total_elapsed * 1000 / iterations + + def test_render_time_for_small_component(self): template = Template("{% load component_tags %}{% component_block 'test_component' %}" "{% slot \"header\" %}{% component 'inner_component' variable='foo' %}{% endslot %}" "{% endcomponent_block %}", name='root') - start_time = perf_counter() - for _ in range(1000): - template.render(Context({})) - end_time = perf_counter() - total_elapsed = end_time - start_time # NOQA - print(f'{total_elapsed } ms per template') + + print(f'{self.timed_loop(lambda: template.render(Context({})))} ms per iteration') + + def test_middleware_time_with_dependency_for_small_page(self): + template = Template("{% load component_tags %}{% component_dependencies %}" + "{% component_block 'test_component' %}{% slot \"header\" %}" + "{% component 'inner_component' variable='foo' %}{% endslot %}{% endcomponent_block %}", + name='root') + # Sanity tests + response = create_and_process_template_response(template) + response_content = response.content.decode('utf-8') + self.assertNotIn(CSS_DEPENDENCY_PLACEHOLDER, response_content) + self.assertNotIn(JS_DEPENDENCY_PLACEHOLDER, response_content) + self.assertIn('style.css', response_content) + self.assertIn('script.js', response_content) + + without_middleware = self.timed_loop(lambda: create_and_process_template_response(template, + use_middleware=False)) + with_middleware = self.timed_loop(lambda: create_and_process_template_response(template, use_middleware=True)) + + print('Small page middleware test') + self.report_results(with_middleware, without_middleware) + + def test_render_time_with_dependency_for_large_page(self): + from django.template.loader import get_template + + template = get_template('mdn_complete_page.html') + response = create_and_process_template_response(template, {}) + response_content = response.content.decode('utf-8') + self.assertNotIn(CSS_DEPENDENCY_PLACEHOLDER, response_content) + self.assertNotIn(JS_DEPENDENCY_PLACEHOLDER, response_content) + self.assertIn('test.css', response_content) + self.assertIn('test.js', response_content) + + without_middleware = self.timed_loop( + lambda: create_and_process_template_response(template, {}, use_middleware=False)) + with_middleware = self.timed_loop( + lambda: create_and_process_template_response(template, {}, use_middleware=True)) + + print('Large page middleware test') + self.report_results(with_middleware, without_middleware) + + @staticmethod + def report_results(with_middleware, without_middleware): + print(f'Middleware active\t\t{with_middleware:.3f} ms per iteration') + print(f'Middleware inactive\t{without_middleware:.3f} ms per iteration') + time_difference = with_middleware - without_middleware + if without_middleware > with_middleware: + print(f'Decrease of {-100 * time_difference / with_middleware:.2f}%') + else: + print(f'Increase of {100 * time_difference / without_middleware:.2f}%') diff --git a/django_components/component.py b/django_components/component.py index f4c5b008..94062772 100644 --- a/django_components/component.py +++ b/django_components/component.py @@ -42,7 +42,7 @@ class SimplifiedInterfaceMediaDefiningClass(MediaDefiningClass): class Component(metaclass=SimplifiedInterfaceMediaDefiningClass): def __init__(self, component_name): - self.__component_name = component_name + self._component_name = component_name self.instance_template = None self.slots = {} @@ -90,7 +90,7 @@ class Component(metaclass=SimplifiedInterfaceMediaDefiningClass): if settings.DEBUG: warnings.warn( "Component {} was provided with unexpected slots: {}".format( - self.__component_name, unexpected_slots + self._component_name, unexpected_slots ) ) for unexpected_slot in unexpected_slots: diff --git a/django_components/middleware.py b/django_components/middleware.py new file mode 100644 index 00000000..5e0b2fb8 --- /dev/null +++ b/django_components/middleware.py @@ -0,0 +1,74 @@ +import re + +from django.conf import settings +from django.forms import Media +from django.http import StreamingHttpResponse + +RENDERED_COMPONENTS_CONTEXT_KEY = "_COMPONENT_DEPENDENCIES" +CSS_DEPENDENCY_PLACEHOLDER = '' +JS_DEPENDENCY_PLACEHOLDER = ' + {% component_css_dependencies %} + Document and website structure - Learn web development | MDN + + + + + + + + + + + + + + + + + + + + {% component_js_dependencies %} + + +
+ +
+ +

Document and website structure

+ {% component 'breadcrumb_component' items=5 %} +
+
+ +
+
+
+
+ +
+
+
+
+ + + +

In addition to defining individual parts of your page (such as "a paragraph" + or "an image"), HTML + also boasts a number of block level elements used to define areas of your website (such as + "the header", "the navigation menu", "the main content column"). This article looks into how + to plan a basic website structure, and write the HTML to represent this structure.

+ + + + + + + + + + + + +
Prerequisites:Basic HTML familiarity, as covered in Getting + started with HTML. HTML text formatting, as covered in HTML + text fundamentals. How hyperlinks work, as covered in Creating + hyperlinks. +
Objective:Learn how to structure your document using semantic tags, and how to work out the + structure of a simple website. +
+
+

Basic sections of a document

+

Webpages can and will look pretty different from one another, but they all tend to share + similar standard components, unless the page is displaying a fullscreen video or game, is part + of some kind of art project, or is just badly structured:

+ +
+
header:
+
Usually a big strip across the top with a big heading, logo, and perhaps a tagline. This + usually stays the same from one webpage to another. +
+
navigation bar:
+
Links to the site's main sections; usually represented by menu buttons, links, or tabs. + Like the header, this content usually remains consistent from one webpage to another — + having inconsistent navigation on your website will just lead to confused, frustrated + users. Many web designers consider the navigation bar to be part of the header rather + than an individual component, but that's not a requirement; in fact, some also argue + that having the two separate is better for accessibility, + as screen readers can read the two features better if they are separate. +
+
main content:
+
A big area in the center that contains most of the unique content of a given webpage, + for example, the video you want to watch, or the main story you're reading, or the map + you want to view, or the news headlines, etc. This is the one part of the website that + definitely will vary from page to page! +
+
sidebar:
+
Some peripheral info, links, quotes, ads, etc. Usually, this is contextual to what is + contained in the main content (for example on a news article page, the sidebar might + contain the author's bio, or links to related articles) but there are also cases where + you'll find some recurring elements like a secondary navigation system. +
+
footer:
+
A strip across the bottom of the page that generally contains fine print, copyright + notices, or contact info. It's a place to put common information (like the header) but + usually, that information is not critical or secondary to the website itself. The footer + is also sometimes used for SEO purposes, + by providing links for quick access to popular content. +
+
+ +

A "typical website" could be structured something like this:

+ +

+ a simple website structure example featuring a main heading, navigation menu, main content, side bar, and footer.

+
+

HTML for structuring content

+

The simple example shown above isn't pretty, but it is perfectly fine for illustrating + a typical website layout example. Some websites have more columns, some are a lot more + complex, but you get the idea. With the right CSS, you could use pretty much any elements to + wrap around the different sections and get it looking how you wanted, but as discussed before, + we need to respect semantics and use the right element for the right job.

+ +

This is because visuals don't tell the whole story. We use color and font size to draw + sighted users' attention to the most useful parts of the content, like the navigation menu + and related links, but what about visually impaired people for example, who might not find + concepts like "pink" and "large font" very useful?

+ +
+

Note: Roughy 8% of men and 0.5% of women are + colorblind; or, to put it another way, approximately 1 in every 12 men and 1 in every + 200 women. Blind and visually impaired people represent roughly 4-5% of the world + population (in 2012 there were 285 million such + people in the world, while the total population was around 7 billion).

+
+ +

In your HTML code, you can mark up sections of content based on their functionality + — you can use elements that represent the sections of content described above unambiguously, + and assistive technologies like screenreaders can recognize those elements and help with + tasks like "find the main navigation", or "find the main content." As we mentioned earlier + in the course, there are a number of consequences + of not using the right element structure and semantics for the right job.

+ +

To implement such semantic mark up, HTML provides dedicated tags that you can use to + represent such sections, for example:

+ + +
+

Active learning: + exploring the code for our example

+

Our example seen above is represented by the following code (you can also find the example in our GitHub repository). We'd like + you to look at the example above, and then look over the listing below to see what parts make up + what section of the visual.

+ +
<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+
+    <title>My page title</title>
+    <link href="https://fonts.googleapis.com/css?family=Open+Sans+Condensed:300|Sonsie+One" rel="stylesheet" type="text/css">
+    <link rel="stylesheet" href="style.css">
+
+    <!-- the below three lines are a fix to get HTML5 semantic elements working in old versions of Internet Explorer-->
+    <!--[if lt IE 9]>
+      <script src="https://cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv.js"></script>
+    <![endif]-->
+  </head>
+
+  <body>
+    <!-- Here is our main header that is used across all the pages of our website -->
+
+    <header>
+      <h1>Header</h1>
+    </header>
+
+    <nav>
+      <ul>
+        <li><a href="#">Home</a></li>
+        <li><a href="#">Our team</a></li>
+        <li><a href="#">Projects</a></li>
+        <li><a href="#">Contact</a></li>
+      </ul>
+
+       <!-- A Search form is another common non-linear way to navigate through a website. -->
+
+       <form>
+         <input type="search" name="q" placeholder="Search query">
+         <input type="submit" value="Go!">
+       </form>
+     </nav>
+
+    <!-- Here is our page's main content -->
+    <main>
+
+      <!-- It contains an article -->
+      <article>
+        <h2>Article heading</h2>
+
+        <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Donec a diam lectus. Set sit amet ipsum mauris. Maecenas congue ligula as quam viverra nec consectetur ant hendrerit. Donec et mollis dolor. Praesent et diam eget libero egestas mattis sit amet vitae augue. Nam tincidunt congue enim, ut porta lorem lacinia consectetur.</p>
+
+        <h3>Subsection</h3>
+
+        <p>Donec ut librero sed accu vehicula ultricies a non tortor. Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aenean ut gravida lorem. Ut turpis felis, pulvinar a semper sed, adipiscing id dolor.</p>
+
+        <p>Pelientesque auctor nisi id magna consequat sagittis. Curabitur dapibus, enim sit amet elit pharetra tincidunt feugiat nist imperdiet. Ut convallis libero in urna ultrices accumsan. Donec sed odio eros.</p>
+
+        <h3>Another subsection</h3>
+
+        <p>Donec viverra mi quis quam pulvinar at malesuada arcu rhoncus. Cum soclis natoque penatibus et manis dis parturient montes, nascetur ridiculus mus. In rutrum accumsan ultricies. Mauris vitae nisi at sem facilisis semper ac in est.</p>
+
+        <p>Vivamus fermentum semper porta. Nunc diam velit, adipscing ut tristique vitae sagittis vel odio. Maecenas convallis ullamcorper ultricied. Curabitur ornare, ligula semper consectetur sagittis, nisi diam iaculis velit, is fringille sem nunc vet mi.</p>
+      </article>
+
+      <!-- the aside content can also be nested within the main content -->
+      <aside>
+        <h2>Related</h2>
+
+        <ul>
+          <li><a href="#">Oh I do like to be beside the seaside</a></li>
+          <li><a href="#">Oh I do like to be beside the sea</a></li>
+          <li><a href="#">Although in the North of England</a></li>
+          <li><a href="#">It never stops raining</a></li>
+          <li><a href="#">Oh well...</a></li>
+        </ul>
+      </aside>
+
+    </main>
+
+    <!-- And here is our main footer that is used across all the pages of our website -->
+
+    <footer>
+      <p>©Copyright 2050 by nobody. All rights reversed.</p>
+    </footer>
+
+  </body>
+</html>
+ +

Take some time to look over the code and understand it — the comments inside the code should + also help you to understand it. We aren't asking you to do much else in this article, + because the key to understanding document layout is writing a sound HTML structure, and then + laying it out with CSS. We'll wait for this until you start to study CSS layout as part of + the CSS topic.

+

HTML layout elements in more + detail

+

It's good to understand the overall meaning of all the HTML sectioning elements in detail — + this is something you'll work on gradually as you start to get more experience with web + development. You can find a lot of detail by reading our HTML element + reference. For now, these are the main definitions that you should try to understand: +

+ +
    +
  • <main> + is for content unique to this page. Use <main> only + once per page, and put it directly inside <body>. + Ideally this shouldn't be nested within other elements. +
  • +
  • <article> + encloses a block of related content that makes sense on its own without the rest of the + page (e.g., a single blog post). +
  • +
  • <section> + is similar to <article>, but it is more for grouping together a + single part of the page that constitutes one single piece of functionality (e.g., a mini + map, or a set of article headlines and summaries), or a theme. It's considered best + practice to begin each section with a heading; + also note that you can break <article>s up into different <section>s, + or <section>s up into different <article>s, + depending on the context. +
  • +
  • <aside> + contains content that is not directly related to the main content but can provide + additional information indirectly related to it (glossary entries, author biography, + related links, etc.). +
  • +
  • <header> + represents a group of introductory content. If it is a child of <body> + it defines the global header of a webpage, but if it's a child of an <article> + or <section> + it defines a specific header for that section (try not to confuse this with titles + and headings). +
  • +
  • <nav> + contains the main navigation functionality for the page. Secondary links, etc., would + not go in the navigation. +
  • +
  • <footer> + represents a group of end content for a page. +
  • +
+ +

Each of the aforementioned elements can be clicked on to read the corresponding article in + the "HTML element reference" section, providing more detail about each one.

+

Non-semantic wrappers

+

Sometimes you'll come across a situation where you can't find an ideal semantic element to + group some items together or wrap some content. Sometimes you might want to just group a set of + elements together to affect them all as a single entity with some CSS or JavaScript. For + cases like these, HTML provides the <div> + and <span> + elements. You should use these preferably with a suitable class + attribute, to provide some kind of label for them so they can be easily targeted.

+ +

+ <span> + is an inline non-semantic element, which you should only use if you can't think of a better + semantic text element to wrap your content, or don't want to add any specific meaning. For + example:

+ +
<p>The King walked drunkenly back to his room at 01:00, the beer doing nothing to aid
+him as he staggered through the door <span class="editor-note">[Editor's note: At this point in the
+play, the lights should be down low]</span>.</p>
+ +

In this case, the editor's note is supposed to merely provide extra direction for the + director of the play; it is not supposed to have extra semantic meaning. For sighted users, + CSS would perhaps be used to distance the note slightly from the main text.

+ +

+ <div> + is a block level non-semantic element, which you should only use if you can't think of a + better semantic block element to use, or don't want to add any specific meaning. For + example, imagine a shopping cart widget that you could choose to pull up at any point during + your time on an e-commerce site:

+ +
<div class="shopping-cart">
+  <h2>Shopping cart</h2>
+  <ul>
+    <li>
+      <p><a href=""><strong>Silver earrings</strong></a>: $99.95.</p>
+      <img src="../products/3333-0985/thumb.png" alt="Silver earrings">
+    </li>
+    <li>
+      ...
+    </li>
+  </ul>
+  <p>Total cost: $237.89</p>
+</div>
+ +

This isn't really an <aside>, as it doesn't necessarily relate to the main + content of the page (you want it viewable from anywhere). It doesn't even particularly + warrant using a  <section>, as it isn't part of the main content of + the page. So a <div> is fine in this case. We've included a heading as a + signpost to aid screenreader users in finding it.

+ +
+

Warning: Divs are so convenient to use that it's easy to use them too + much. As they carry no semantic value, they just clutter your HTML code. Take care to + use them only when there is no better semantic solution and try to reduce their usage to + the minimum otherwise you'll have a hard time updating and maintaining your documents. +

+
+
+

Line breaks and horizontal rules +

+

Two elements that you'll use occasionally and will want to know about are <br> + and + <hr>: +

+ +

<br> creates a line break in a paragraph; it is the only way to force a + rigid structure in a situation where you want a series of fixed short lines, such as in a + postal address or a poem. For example:

+ +
+
<p>There once was a man named O'Dell<br>
+Who loved to write HTML<br>
+But his structure was bad, his semantics were sad<br>
+and his markup didn't read very well.</p>
+
+ +

Without the <br> elements, the paragraph would just be rendered in one + long line (as we said earlier in the course, HTML + ignores most whitespace); with <br> elements in the code, the + markup renders like this:

+ +

+ +

+ +

<hr> elements create a horizontal rule in the document that denotes a + thematic change in the text (such as a change in topic or scene). Visually it just looks + like a horizontal line. As an example:

+ +
+
<p>Ron was backed into a corner by the marauding netherbeasts. Scared, but determined to protect his friends, he raised his wand and prepared to do battle, hoping that his distress call had made it through.</p>
+<hr>
+<p>Meanwhile, Harry was sitting at home, staring at his royalty statement and pondering when the next spin off series would come out, when an enchanted distress letter flew through his window and landed in his lap. He read it hazily and sighed; "better get back to work then", he mused.</p>
+
+ +

Would render like this:

+ +

+ +

+
+

Planning a simple website

+

Once you've planned out the structure of a simple webpage, the next logical step is to try + to work out what content you want to put on a whole website, what pages you need, and how they + should be arranged and link to one another for the best possible user experience. This is called + Information + architecture. In a large, complex website, a lot of planning can go into this process, + but for a simple website of a few pages, this can be fairly simple, and fun!

+ +
    +
  1. Bear in mind that you'll have a few elements common to most (if not all) pages — such as + the navigation menu, and the footer content. If your site is for a business, for + example, it's a good idea to have your contact information available in the footer on + each page. Note down what you want to have common to every page.the common features of the travel site to go on every page: title and logo, contact, copyright, terms and conditions, language chooser, accessibility policy
  2. +
  3. Next, draw a rough sketch of what you might want the structure of each page to look like + (it might look like our simple website above). Note what each block is going to be.A simple diagram of a sample site structure, with a header, main content area, two optional sidebars, and footer
  4. +
  5. Now, brainstorm all the other (not common to every page) content you want to have on + your website — write a big list down.A long list of all the features that we could put on our travel site, from searching, to special offers and country-specific info
  6. +
  7. Next, try to sort all these content items into groups, to give you an idea of what parts + might live together on different pages. This is very similar to a technique called Card + sorting.The items that should appear on a holiday site sorted into 5 categories: Search, Specials, Country-specific info, Search results, and Buy things
  8. +
  9. Now try to sketch a rough sitemap — have a bubble for each page on your site, and draw + lines to show the typical workflow between pages. The homepage will probably be in the + center, and link to most if not all of the others; most of the pages in a small site + should be available from the main navigation, although there are exceptions. You might + also want to include notes about how things might be presented.A map of the site showing the homepage, country page, search results, specials page, checkout, and buy page
  10. +
+
+

Active learning: create your + own sitemap

+

Try carrying out the above exercise for a website of your own creation. What would you like + to make a site about?

+ +
+

Note: Save your work somewhere; you might need it later on.

+
+
+

Test your skills!

+

You've reached the end of this article, but can you remember the most important information? + You can find a detailed assessment that tests these skills at the end of the module; see Structuring + a page of content. We'd advise going through the next article in the series first and + not just skipping to it though!

+

Summary

+

At this point you should have a better idea about how to structure a web page/site. In the + last article of this module, we'll study how to debug HTML.

+

See also

+
+ + +

+ +

+

In this module

+ +
+
+ +
+ + +
+ + + + \ No newline at end of file diff --git a/tests/templates/mdn_component_template.html b/tests/templates/mdn_component_template.html new file mode 100644 index 00000000..992b330f --- /dev/null +++ b/tests/templates/mdn_component_template.html @@ -0,0 +1,14 @@ + \ No newline at end of file diff --git a/tests/test_context.py b/tests/test_context.py index 00b33afb..7ceae11b 100644 --- a/tests/test_context.py +++ b/tests/test_context.py @@ -215,28 +215,27 @@ class ContextCalledOnceTests(SimpleTestCase): self.assertEqual(rendered, '

value=1;calls=1

', rendered) def test_one_context_call_with_simple_component_and_arg(self): - template = Template("{% load component_tags %}{% component_dependencies %}" - "{% component name='incrementer' value='2' %}") + template = Template("{% load component_tags %}{% component name='incrementer' value='2' %}") rendered = template.render(Context()).strip() self.assertEqual(rendered, '

value=3;calls=1

', rendered) def test_one_context_call_with_component_block(self): - template = Template("{% load component_tags %}{% component_dependencies %}" + template = Template("{% load component_tags %}" "{% component_block 'incrementer' %}{% endcomponent_block %}") rendered = template.render(Context()).strip() self.assertEqual(rendered, '

value=1;calls=1

', rendered) def test_one_context_call_with_component_block_and_arg(self): - template = Template("{% load component_tags %}{% component_dependencies %}" + template = Template("{% load component_tags %}" "{% component_block 'incrementer' value='3' %}{% endcomponent_block %}") rendered = template.render(Context()).strip() self.assertEqual(rendered, '

value=4;calls=1

', rendered) def test_one_context_call_with_slot(self): - template = Template("{% load component_tags %}{% component_dependencies %}" + template = Template("{% load component_tags %}" "{% component_block 'incrementer' %}{% slot 'content' %}" "

slot

{% endslot %}{% endcomponent_block %}") rendered = template.render(Context()).strip() @@ -244,7 +243,7 @@ class ContextCalledOnceTests(SimpleTestCase): self.assertEqual(rendered, '

value=1;calls=1

\n

slot

', rendered) def test_one_context_call_with_slot_and_arg(self): - template = Template("{% load component_tags %}{% component_dependencies %}" + template = Template("{% load component_tags %}" "{% component_block 'incrementer' value='3' %}{% slot 'content' %}" "

slot

{% endslot %}{% endcomponent_block %}") rendered = template.render(Context()).strip() diff --git a/tests/test_dependency_rendering.py b/tests/test_dependency_rendering.py new file mode 100644 index 00000000..9d26eaa3 --- /dev/null +++ b/tests/test_dependency_rendering.py @@ -0,0 +1,202 @@ +from django.template import Template +from django.test import override_settings + +from .django_test_setup import * # NOQA +from django_components import component + +from .test_templatetags import SimpleComponent +from .testutils import create_and_process_template_response, Django30CompatibleSimpleTestCase as SimpleTestCase + + +class SimpleComponentAlternate(component.Component): + def context(self, variable): + return {} + + def template(self, context): + return "simple_template.html" + + class Media: + css = "style2.css" + js = "script2.js" + + +class SimpleComponentWithSharedDependency(component.Component): + def context(self, variable, variable2="default"): + return { + "variable": variable, + "variable2": variable2, + } + + def template(self, context): + return "simple_template.html" + + class Media: + css = ["style.css", "style2.css"] + js = ["script.js", "script2.js"] + + +class MultistyleComponent(component.Component): + def template(self, context): + return "simple_template.html" + + class Media: + css = ["style.css", "style2.css"] + js = ["script.js", "script2.js"] + + +@override_settings(COMPONENTS={'RENDER_DEPENDENCIES': True}) +class ComponentMediaRenderingTests(SimpleTestCase): + def setUp(self): + # NOTE: component.registry is global, so need to clear before each test + component.registry.clear() + + def test_no_dependencies_when_no_components_used(self): + component.registry.register(name="test", component=SimpleComponent) + + template = Template("{% load component_tags %}{% component_dependencies %}") + rendered = create_and_process_template_response(template) + self.assertInHTML('""" - ) - self.assertHTMLEqual(rendered, dedent(expected_outcome)) - - def test_single_component_css_dependencies(self): - component.registry.register(name="test", component=SimpleComponent) - - template = Template("{% load component_tags %}{% component_css_dependencies %}") - rendered = template.render(Context()) - expected_outcome = ( - """""" - ) - self.assertHTMLEqual(rendered, dedent(expected_outcome)) - - def test_single_component_js_dependencies(self): - component.registry.register(name="test", component=SimpleComponent) - - template = Template("{% load component_tags %}{% component_js_dependencies %}") - rendered = template.render(Context()) - expected_outcome = ( - """""" - ) - self.assertHTMLEqual(rendered, dedent(expected_outcome)) - def test_single_component(self): component.registry.register(name="test", component=SimpleComponent) @@ -145,40 +114,6 @@ class ComponentTemplateTagTest(SimpleTestCase): rendered = template.render(Context({})) self.assertHTMLEqual(rendered, "Variable: variable\n") - def test_multiple_component_dependencies(self): - component.registry.register(name="test1", component=SimpleComponent) - component.registry.register(name="test2", component=SimpleComponent) - - template = Template("{% load component_tags %}{% component_dependencies %}") - rendered = template.render(Context()) - expected_outcome = ( - """\n""" - """""" - ) - self.assertHTMLEqual(rendered, dedent(expected_outcome)) - - def test_multiple_component_css_dependencies(self): - component.registry.register(name="test1", component=SimpleComponent) - component.registry.register(name="test2", component=SimpleComponent) - - template = Template("{% load component_tags %}{% component_css_dependencies %}") - rendered = template.render(Context()) - expected_outcome = ( - """""" - ) - self.assertHTMLEqual(rendered, dedent(expected_outcome)) - - def test_multiple_component_js_dependencies(self): - component.registry.register(name="test1", component=SimpleComponent) - component.registry.register(name="test2", component=SimpleComponent) - - template = Template("{% load component_tags %}{% component_js_dependencies %}") - rendered = template.render(Context()) - expected_outcome = ( - """""" - ) - self.assertHTMLEqual(rendered, dedent(expected_outcome)) - class ComponentSlottedTemplateTagTest(SimpleTestCase): def setUp(self): diff --git a/tests/testutils.py b/tests/testutils.py index bee19476..818e4d4a 100644 --- a/tests/testutils.py +++ b/tests/testutils.py @@ -1,7 +1,43 @@ -from django.test import SimpleTestCase +from unittest.mock import Mock + +from django.template import Context +from django.template.response import TemplateResponse +from django.test import SimpleTestCase, TestCase + +from django_components.middleware import ComponentDependencyMiddleware + +# Create middleware instance +response_stash = None +middleware = ComponentDependencyMiddleware(get_response=lambda _: response_stash) class Django30CompatibleSimpleTestCase(SimpleTestCase): def assertHTMLEqual(self, left, right): left = left.replace(' type="text/javascript"', '') super(Django30CompatibleSimpleTestCase, self).assertHTMLEqual(left, right) + + def assertInHTML(self, needle, haystack, count=None, msg_prefix=''): + haystack = haystack.replace(' type="text/javascript"', '') + super().assertInHTML(needle, haystack, count, msg_prefix) + + +class Django30CompatibleTestCase(Django30CompatibleSimpleTestCase, TestCase): + pass + + +request = Mock() +mock_template = Mock() + + +def create_and_process_template_response(template, context=None, use_middleware=True): + context = context if context is not None else Context({}) + mock_template.render = lambda context, _: template.render(context) + response = TemplateResponse(request, mock_template, context) + if use_middleware: + response.render() + global response_stash + response_stash = response + response = middleware(request) + else: + response.render() + return response.content.decode('utf-8')