Merge lp:~sinzui/launchpad/launchpad-header-0 into lp:launchpad

Proposed by Curtis Hovey
Status: Merged
Approved by: Paul Hummer
Approved revision: no longer in the source branch.
Merged at revision: 11229
Proposed branch: lp:~sinzui/launchpad/launchpad-header-0
Merge into: lp:launchpad
Diff against target: 1273 lines (+397/-542)
13 files modified
lib/canonical/launchpad/doc/hierarchical-menu.txt (+20/-14)
lib/canonical/launchpad/icing/style-3-0.css.in (+28/-13)
lib/lp/app/browser/tests/base-layout.txt (+1/-280)
lib/lp/app/browser/tests/test_base_layout.py (+163/-0)
lib/lp/app/templates/base-layout-macros.pt (+1/-1)
lib/lp/app/templates/base-layout.pt (+18/-13)
lib/lp/app/templates/launchpad-hierarchy.pt (+4/-4)
lib/lp/blueprints/stories/sprints/05-sprint-creation.txt (+99/-117)
lib/lp/bugs/stories/bugs/xx-bug-heat-on-bug-page.txt (+4/-4)
lib/lp/bugs/stories/bugs/xx-malone-homepage.txt (+10/-18)
lib/lp/bugs/stories/bugtracker/bugtrackers-index.txt (+4/-11)
lib/lp/bugs/stories/cve/cve-pages.txt (+40/-65)
lib/lp/bugs/templates/bugtask-index.pt (+5/-2)
To merge this branch: bzr merge lp:~sinzui/launchpad/launchpad-header-0
Reviewer Review Type Date Requested Status
Paul Hummer (community) ui Approve
Brad Crittenden (community) code Approve
Robert Collins (community) Abstain
Review via email: mp+30570@code.launchpad.net

Description of the change

This is my branch to fix the launchpad header so for the title edit widget.
This branch fixes a number of visual issues in the header, but in doing so,
it call attention to the registered slot. Michael and I decided that the
registration slot is out of scope for this branch and the probable solution
is to move it, which requires a reexamination of all its uses.

    lp:~sinzui/launchpad/owner-driver-supervisor
    Diff size: 643
    Launchpad bug: https://bugs.launchpad.net/bugs/538028
    Test command: ./bin/test -vv -t test_base_layout -t base-layout
    Pre-implementation: noodles
    Target release: 10.08

Fix the launchpad header so for the title edit widget
-----------------------------------------------------

The floated left and right instructions in the launchpad header break
the layout of the title edit widget; The widget disassembles itself trying
to avoid the floated content.

Rules
-----

    * Consider using inline-block CSS and relative positioning to fix the
      issue.

    Michael suggested:
    * ADDENDUM: Fix the leading white space before the watermark.
    * ADDENDUM: Align the apps with the bottom of the icon so that there is
      no extra white space between the context, apps, heading and bead crumbs.

    Curtis:
    * Michael's suggestions are very good. They make other problems in the
      header more obvious. The registration slot is taking advantage of the
      unwanted white space. It will be obvious that it was an after thought
      when it creates an extra line. If it is clear that the apps are the
      second part of the header, it may be clear to some users they appear
      in the wrong place in the bread crumbs.

    * ADDENDUM: Move the check for bread crumbs to the list element so that
      no part of the block is rendered when there is no bread crumbs.

QA
--

    * Visit https://edge.launchpad.net/launchpad
    * Choose the edit the title
    * Verify the widget stays together and remains adjacent to the logo.
    * Visit https://bugs.edge.launchpad.net/launchpad-web/+bug/538028
    * Verify the context, apps, title, and bread crumbs are together...
      the reported by line does not interrupt.
    * Visit https://bugs.edge.launchpad.net/launchpad-web
    * Verify the context, apps, title, and bread crumbs are together

Lint
----

Linting changed files:
  lib/canonical/launchpad/icing/style-3-0.css.in
  lib/lp/app/browser/tests/base-layout.txt
  lib/lp/app/browser/tests/test_base_layout.py
  lib/lp/app/templates/base-layout-macros.pt
  lib/lp/app/templates/base-layout.pt
  lib/lp/app/templates/launchpad-hierarchy.pt

Test
----

    * lib/lp/app/browser/tests/base-layout.txt
      * Removed big chunks of a very brittle test that broke from the minor
        markup changes. The diff shows the tests for common parts were
        different, but they where/should be the same. I decided to move
        the testing to a unittest. Note that the diff /before/ my decision to
        to replace the tests was long and hard to read.
    * lib/lp/app/browser/tests/test_base_layout.py
      * Moved the tests for the parts that base-layout provides to unittests.
        I should now be obvious when parts each layout provides.

Implementation
--------------

    * lib/canonical/launchpad/icing/style-3-0.css.in
      * Added .flowed-block which allows us to layout horizontal chunks
        of block content like we image it in our heads.
      * Replaced #locationbar with.login-logout which accidentally solved
        Michaels concern about the leading space :)
      * Updated watermark-apps-portlet rules to not wrap and clear any floated
        content so that the title edit widget does not need to avoid other
        parts of the header.
      * Removed the excess whitespace from .registering and #logincontrol
    * lib/lp/app/templates/base-layout-macros.pt
      * Added an id to make the content easy to test.
    * lib/lp/app/templates/base-layout.pt
      * Wrapped the problem code in flowed-block divs.
      * Moved the registration slot below the logo so that there was no
        floated content near the heading that the title edit widget wraps.
      * Added ids to make content testable.
    * lib/lp/app/templates/launchpad-hierarchy.pt
      * Moved the bread crumb test condition to the block element...the page
        was always rendering space for the bread crumbs.

To post a comment you must log in.
Revision history for this message
Robert Collins (lifeless) wrote :

This sounds nice, but I'm not comfortable enough with this part of the system yet to be your reviewer - sorry!

review: Abstain
Revision history for this message
Curtis Hovey (sinzui) wrote :

These are the three headers Michael and I looked at. They represent the best and worst of how the header works. In all cases the space before the logo and the space without bread crumbs is fixed \o/. The title edit widget hold together since it does not need to dodge the left and right content. By aligning the primary context title and apps with the bottom of the logo, view.png shows what we wanted users to see. But the two context images have a registration line that inserts a blank line. Registration always did this, it was not as obvious.

http://people.canonical.com/~curtis/primary-context.png
http://people.canonical.com/~curtis/title-edit-widget.png
http://people.canonical.com/~curtis/view.png
http://people.canonical.com/~curtis/secondary-context.png

I ponder moving the registation before or after the bread crumbs. We want the registration to be adjacent to the title of the context. This was/is not possible for primary contexts. We rely on the sparceness of the header to imply that the context is the only thing the registration text can pertain to. Placing the registration before the breadcrumbs, a, returns to the orginal design for 3.0, and b, puts the information closer to the primary context logo, and the secondary context title when it exists.

/me hacks to make screen caps.

Revision history for this message
Curtis Hovey (sinzui) wrote :

I moved the registration slot into context-location area, it it between the context title and bread crumbs. I made the text the same size as the bread crumbs.

http://people.canonical.com/~curtis/better-primary-context.png
http://people.canonical.com/~curtis/better-title-edit.png
http://people.canonical.com/~curtis/better-view.png
http://people.canonical.com/~curtis/better-secondary-context.png

These changes did not inflate the diff. I updated a test to verify the content moved.

The bug page did look bad, and that is because it violates the registration slot rules...the
data is immutable; it is not a status. I moved the heat inline and updated the one affected
test. Another was robust. I removed the redundant bug number too (no tests broke).
./bin/test -vvc -t bug-heat-view -t xx-bug-heat-on-bug-page

Revision history for this message
Brad Crittenden (bac) wrote :

Robert, if you claim a review from the team and then abstain the review disappears from the view of other reviewers. To remedy, select 'Request another review' and assign it to the Launchpad Reviewers team. (Sorry for cluttering the MP with these instructions but it makes sense since it provides context.)

Revision history for this message
Brad Crittenden (bac) wrote :

Curtis I think these changes really clean up the top of the page. The code looks good. Another round of UI review will be beneficial.

This docstring needs a final period:
"""Test for the ILaunchpadRoot permission"""

review: Approve (code)
Revision history for this message
Curtis Hovey (sinzui) wrote :

Hi Paul.

In summary. The start of the review says I did not want to fix the registration slot issue, and you can see pictures of how fixing the white space made the problem very obvious. The more recent comments with the better-* set of images shows I decided to move the registration slot anyway. I think this is an improvement, though I would not ever claim it is perfect.

Revision history for this message
Paul Hummer (rockstar) wrote :

This looks REALLY good. Thanks for dealing with it. The header can sometimes be an eyesore, so anything we can do to make it better is something I'm onboard with.

review: Approve (ui)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/canonical/launchpad/doc/hierarchical-menu.txt'
2--- lib/canonical/launchpad/doc/hierarchical-menu.txt 2010-01-22 17:28:32 +0000
3+++ lib/canonical/launchpad/doc/hierarchical-menu.txt 2010-07-26 13:34:58 +0000
4@@ -1,15 +1,16 @@
5-= Hierarchical menus =
6+Hierarchical menus
7+==================
8
9 The location bar aids users in navigating the depths of Launchpad. It
10 is built from a list of Breadcrumb objects collected during Zope's
11 object-traversal step.
12
13-== A simple object hierarchy ==
14+A simple object hierarchy
15+-------------------------
16
17 First, we need a hierarchy of objects to build upon:
18
19- >>> from zope.component import (getMultiAdapter, provideAdapter,
20- ... queryAdapter)
21+ >>> from zope.component import getMultiAdapter, provideAdapter
22 >>> from zope.interface import Interface, implements
23
24 >>> class ICookbook(Interface):
25@@ -56,7 +57,8 @@
26 >>> recipe = Recipe('spam', cookbook)
27
28
29-== Discovering breadcrumbs ==
30+Discovering breadcrumbs
31+-----------------------
32
33 The Hierarchy class builds the breadcrumbs by looking at each object in
34 the request.traversed_objects attribute. If a traversed object can be
35@@ -124,10 +126,11 @@
36 >>> cooker_hierarchy = getMultiAdapter(
37 ... (cooker, cooker_request), name='+hierarchy')
38 >>> cooker_hierarchy.items
39- [<TextualBreadcrumb url='http://launchpad.dev/+cooker/jamie' text='Jamie'>]
40-
41-
42-== Displaying breadcrumbs ==
43+ [<TextualBreadcrumb url='.../+cooker/jamie' text='Jamie'>]
44+
45+
46+Displaying breadcrumbs
47+----------------------
48
49 Breadcrumbs are only displayed if there is more than one breadcrumb, as
50 otherwise the breadcrumb will simply replicate the context.title heading
51@@ -163,7 +166,8 @@
52 False
53
54
55-== Building IBreadcrumb objects ==
56+Building IBreadcrumb objects
57+----------------------------
58
59 The construction of breadcrumb objects is handled by an IBreadcrumb adapter,
60 which adapts a context object and produces an IBreadcrumb object for that
61@@ -195,7 +199,8 @@
62 text='Joy of cooking'>
63
64
65-== Customizing the hierarchy ==
66+Customizing the hierarchy
67+-------------------------
68
69 We can customize the hierarchy itself by changing the list of objects
70 and URLs that it uses to construct the breadcrumbs list.
71@@ -216,7 +221,8 @@
72 text='Spam'>]
73
74
75-== Rendering the list ==
76+Rendering the list
77+------------------
78
79 The Hierarchy object is responsible for rendering the HTML for the
80 location bar.
81@@ -261,5 +267,5 @@
82 >>> homepage_hierarchy.items
83 []
84
85- >>> print_hierarchy(homepage_hierarchy.render())
86- Location:
87+ >>> homepage_hierarchy.render().strip()
88+ ''
89
90=== modified file 'lib/canonical/launchpad/icing/style-3-0.css.in'
91--- lib/canonical/launchpad/icing/style-3-0.css.in 2010-07-22 16:27:12 +0000
92+++ lib/canonical/launchpad/icing/style-3-0.css.in 2010-07-26 13:34:58 +0000
93@@ -709,6 +709,13 @@
94 .batch-navigation-links .next {
95 margin-right: 0.25em;
96 }
97+.flowed-block {
98+ display: table-cell;
99+ display: inline-table;
100+ display: inline-block;
101+ text-align: left;
102+ vertical-align: top;
103+ }
104
105
106 /* =========================
107@@ -1111,18 +1118,22 @@
108 .demo {
109 background-color: #fee;
110 }
111-#locationbar {
112- /* The following style works around the fact that the locationbar, which
113- is only used of the Login/Register links in 3.0 UI, is accessible on
114- "locationless" pages. It adds extra space at the top of every page in
115- Launchpad but that does actually look quite good ... ;-).*/
116- height: 1em;
117+.login-logout {
118+ position: absolute;
119+ top: .5em;
120+ right: 1.5em;
121 }
122 div.watermark-apps-portlet {
123- padding-bottom:1em;
124+ clear: both;
125+ margin-bottom: .5em;
126+ white-space: no-wrap;
127+ }
128+div.watermark-apps-portlet .wide {
129+ width: 75%;
130+ vertical-align: bottom;
131+ margin-bottom: 0.3em;
132 }
133 div.watermark-apps-portlet img {
134- float: left;
135 margin: 0 1.5em 0 0;
136 }
137 div.watermark-apps-portlet h1, div.watermark-apps-portlet h2 {
138@@ -1176,19 +1187,20 @@
139 width: 100%;
140 white-space: nowrap;
141 }
142+.context-publication {
143+ margin-bottom: 1em;
144+ }
145 .registering {
146 /* Registered slot */
147- float: right;
148- font-size: 85%;
149+ margin: 0 0 0.2em 0;
150+ font-style: italic;
151+ font-size: 77%;
152 color: #666;
153- position: relative;
154- top: 2em;
155 }
156 .breadcrumbs {
157 margin-left: 0;
158 list-style-type: none;
159 clear: both;
160- margin-bottom: 2em;
161 font-size: 77%;
162 }
163 .breadcrumbs li {
164@@ -1217,6 +1229,9 @@
165 /* align the image with the text */
166 margin-bottom: -2px;
167 }
168+#logincontrol form {
169+ margin: 0;
170+ }
171 #logincontrol input[type='submit'] {
172 /* The button lacks the right margin that buttons usually have: */
173 font-size: 77%;
174
175=== modified file 'lib/lp/app/browser/tests/base-layout.txt'
176--- lib/lp/app/browser/tests/base-layout.txt 2010-07-08 15:26:09 +0000
177+++ lib/lp/app/browser/tests/base-layout.txt 2010-07-26 13:34:58 +0000
178@@ -35,101 +35,12 @@
179 main and side content are positioned using the "yui-t4", "yui-main",
180 "yui-b", and "yui-b side" classes.
181
182-Header.
183+ >>> from canonical.launchpad.testing.pages import find_tag_by_id
184
185 >>> view = MainSideView(user, request)
186 >>> html = view.render()
187 >>> print html
188 <!DOCTYPE html ...
189- <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"
190- lang="en" dir="ltr">
191- <head>
192- <title>Test base-layout: main_side</title>
193- <link rel="shortcut icon" href="/@@/launchpad.png" />
194- ...
195-
196-Standard Javascript functions.
197-
198- >>> print html
199- <!DOCTYPE html ...
200- ...
201- sortables_init();
202- initInlineHelp();
203- Y.lp.activate_collapsibles();
204- activateFoldables();
205- activateConstrainBugExpiration();
206- ...
207-
208-Body preamble.
209-
210- >>> from canonical.launchpad.testing.pages import find_tag_by_id
211-
212- >>> body_tag = find_tag_by_id(html, 'document')
213- >>> body = str(body_tag)
214- >>> print body
215- <body id="document"
216- class="tab-overview
217- main_side
218- public
219- yui-skin-sam">
220- <div class="yui-d0">
221- <BLANKLINE>
222- <div id="locationbar">
223- ...
224-
225-Watermark and breadcrumbs.
226-
227- >>> print body
228- <body ...
229- ...
230- <div class="watermark-apps-portlet top-portlet">
231- <img alt="" width="64" height="64" src="/@@/person-logo" />
232- <h2>Waffles</h2>
233- ...
234- <!-- Application Menu -->
235- <ul class="facetmenu">
236- <BLANKLINE>
237- <li class="overview active"
238- title="General information about Waffles"><a
239- href="http://launchpad.dev/~waffles">Overview</a></li>
240- ...
241- <div class="yui-b" dir="ltr">
242- <div>
243- <h2>Heading</h2>
244- <ol class="breadcrumbs">
245- ...
246-
247-Top portlet.
248-
249- >>> print body
250- <body ...
251- <div class="top-portlet">
252- <p class="registered">
253- Registered on 2005-09-16
254- by <a class="sprite team" href="#">Illuminati team</a>
255- </p>
256- <p>
257- Main content of the page.
258- </p>
259- </div>
260- ...
261-
262-Help pane.
263-
264- >>> print str(find_tag_by_id(body_tag, 'help-pane'))
265- <div id="help-pane" class="invisible">
266- <div id="help-body">
267- <iframe id="help-pane-content" class="invisible" ...
268- </div>
269- <div id="help-footer">
270- <span id="help-close"></span>
271- </div>
272- </div>
273-
274-Footer.
275-
276- >>> print html
277- <!DOCTYPE html ...
278 <!--
279 Facet name: overview
280 Page type: main_side
281@@ -151,72 +62,6 @@
282
283 >>> view = MainOnlyView(user, request)
284 >>> html = view.render()
285-
286-Heading.
287-
288- >>> print html
289- <!DOCTYPE html ...
290- <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"
291- lang="en" dir="ltr">
292- <head>
293- <title>Test base-layout: main_only</title>
294- <link rel="shortcut icon" href="/@@/launchpad.png" />
295- ...
296-
297-Watermark.
298-
299- >>> body_tag = find_tag_by_id(html, 'document')
300- >>> body = str(body_tag)
301- >>> print body
302- <body ...
303- <div class="watermark-apps-portlet top-portlet">
304- <img alt="" width="64" height="64" src="/@@/person-logo" />
305- <h2>Waffles</h2>
306- ...
307-
308-Main content.
309-
310- >>> print str(find_tag_by_id(body_tag, 'maincontent'))
311- <div id="maincontent" class="yui-main">
312- <div class="yui-b" dir="ltr">
313- <div>
314- <BLANKLINE>
315- <ol class="breadcrumbs">
316- <BLANKLINE>
317- </ol>
318- <BLANKLINE>
319- </div>
320- <BLANKLINE>
321- <BLANKLINE>
322- <BLANKLINE>
323- <div class="top-portlet">
324- <h1>Heading</h1>
325- <p class="registered">
326- Registered on 2005-09-16
327- by <a class="sprite team" href="#">Illuminati team</a>
328- </p>
329- <p>
330- Main content of the page.
331- </p>
332- </div>
333- </div><!-- yui-b -->
334- </div>
335-
336-Global search.
337-
338- >>> print body
339- <body ...
340- <form id="globalsearch" method="get"
341- accept-charset="UTF-8"
342- action="http://launchpad.dev/+search">
343- <input type="search" id="search-text" name="field.text" />
344- <input type="submit" value="" class="icon-only sprite search-icon" />
345- </form>
346- </div>
347- ...
348-
349-Footer.
350-
351 >>> print html
352 <!DOCTYPE html ...
353 ...
354@@ -240,83 +85,6 @@
355
356 >>> view = SearchlessView(user, request)
357 >>> html = view.render()
358-
359-Heading.
360-
361- >>> print html
362- <!DOCTYPE html ...
363- <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"
364- lang="en" dir="ltr">
365- <head>
366- <title>Test base-layout: searchless</title>
367- <link rel="shortcut icon" href="/@@/launchpad.png" />
368- ...
369-
370-Body class.
371-
372- >>> body_tag = find_tag_by_id(html, 'document')
373- >>> body = str(body_tag)
374- >>> print body
375- <body id="document"
376- class="tab-overview
377- searchless
378- public
379- yui-skin-sam">
380- <div class="yui-d0">
381- ...
382-
383-Watermarks.
384-
385- >>> print body
386- <body ...
387- <div class="watermark-apps-portlet top-portlet">
388- <img alt="" width="64" height="64" src="/@@/person-logo" />
389- <h2>Waffles</h2>
390- <div id="registration" class="registering">
391- </div>
392- <BLANKLINE>
393- <!-- Application Menu -->
394- <ul class="facetmenu">
395- <BLANKLINE>
396- <li class="overview active"
397- title="General information about Waffles"><a
398- href="http://launchpad.dev/~waffles">Overview</a></li>
399- ...
400-
401-Main content.
402-
403- >>> print str(find_tag_by_id(body_tag, 'maincontent'))
404- <div id="maincontent" class="yui-main">
405- <div class="yui-b" dir="ltr">
406- <div>
407- <BLANKLINE>
408- <ol class="breadcrumbs">
409- <BLANKLINE>
410- </ol>
411- ...
412-
413-Top portlet.
414-
415- >>> print body
416- <body ...
417- <div class="top-portlet">
418- <h1>Heading</h1>
419- <p class="registered">
420- Registered on 2005-09-16
421- by <a class="sprite team" href="#">Illuminati team</a>
422- </p>
423- <p>
424- Main content of the page.
425- </p>
426- </div>
427- </div><!-- yui-b -->
428- </div><!-- yui-main -->
429- <!-- yui-b side -->
430- <!-- yui-t4 -->
431- ...
432-
433-Footer.
434-
435 >>> print html
436 <!DOCTYPE html ...
437 ...
438@@ -342,51 +110,6 @@
439
440 >>> view = LocationlessView(user, request)
441 >>> html = view.render()
442-
443-Heading.
444-
445- >>> print html
446- <!DOCTYPE html ...
447- <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"
448- lang="en" dir="ltr">
449- <head>
450- <title>Test base-layout: locationless</title>
451- <link rel="shortcut icon" href="/@@/launchpad.png" />
452- ...
453-
454-Body.
455-
456- >>> body_tag = find_tag_by_id(html, 'document')
457- >>> body = str(body_tag)
458- >>> print body
459- <body id="document"
460- class="tab-overview
461- locationless
462- public
463- yui-skin-sam">
464- <div class="yui-d0">
465- ...
466-
467-Main content.
468-
469- >>> print str(find_tag_by_id(body_tag, 'maincontent'))
470- <div id="maincontent" class="yui-main">
471- <div class="yui-b" dir="ltr">
472- <div class="top-portlet">
473- <h1>Heading</h1>
474- <p class="registered">
475- Registered on 2005-09-16
476- by <a class="sprite team" href="#">Illuminati team</a>
477- </p>
478- <p>
479- Main content of the page.
480- </p>
481- </div>
482- </div><!-- yui-b -->
483- </div>
484-
485-Footer.
486-
487 >>> print html
488 <!DOCTYPE html ...
489 ...
490@@ -517,8 +240,6 @@
491 >>> body_tag = find_tag_by_id(view.render(), 'maincontent')
492 >>> print str(body_tag)
493 <div id="maincontent" ...
494- <ol class="breadcrumbs">
495- </ol>
496 ...
497 <div class="informational message">I cannot do that Dave.</div>
498 <div class="top-portlet">
499
500=== added file 'lib/lp/app/browser/tests/test_base_layout.py'
501--- lib/lp/app/browser/tests/test_base_layout.py 1970-01-01 00:00:00 +0000
502+++ lib/lp/app/browser/tests/test_base_layout.py 2010-07-26 13:34:58 +0000
503@@ -0,0 +1,163 @@
504+# Copyright 2010 Canonical Ltd. This software is licensed under the
505+# GNU Affero General Public License version 3 (see the file LICENSE).
506+
507+"""Tests for base-layout.pt and its macros.
508+
509+The base-layout master template defines macros that control the layout
510+of the page. Any page can use these layout options by including
511+
512+ metal:use-macro='view/macro:page/<layout>"
513+
514+in the root element. The template provides common layout to Launchpad.
515+"""
516+
517+__metaclass__ = type
518+
519+from BeautifulSoup import BeautifulSoup
520+
521+from z3c.ptcompat import ViewPageTemplateFile
522+
523+from canonical.launchpad.webapp.publisher import LaunchpadView
524+from canonical.launchpad.webapp.servers import LaunchpadTestRequest
525+from canonical.testing.layers import DatabaseFunctionalLayer
526+from canonical.launchpad.testing.pages import find_tag_by_id
527+
528+from lp.testing import TestCaseWithFactory
529+
530+
531+class TestBaseLayout(TestCaseWithFactory):
532+ """Test the page parts provided by the base-layout.pt."""
533+ layer = DatabaseFunctionalLayer
534+
535+ def setUp(self):
536+ super(TestBaseLayout, self).setUp()
537+ self.user = self.factory.makePerson(name='waffles')
538+ self.request = LaunchpadTestRequest(
539+ SERVER_URL='http://launchpad.dev',
540+ PATH_INFO='/~waffles/+layout')
541+ self.request.setPrincipal(self.user)
542+
543+ def makeTemplateView(self, layout):
544+ """Return a view that uses the specified layout."""
545+
546+ class TemplateView(LaunchpadView):
547+ """A simple view to test base-layout."""
548+ __launchpad_facetname__ = 'overview'
549+ template = ViewPageTemplateFile(
550+ 'testfiles/%s.pt' % layout.replace('_', '-'))
551+ page_title = 'Test base-layout: %s' % layout
552+
553+ return TemplateView(self.user, self.request)
554+
555+ def test_base_layout_doctype(self):
556+ # Verify that the document is a html DOCTYPE.
557+ view = self.makeTemplateView('main_side')
558+ markup = view()
559+ self.assertTrue(markup.startswith('<!DOCTYPE html'))
560+
561+ def verify_base_layout_html_element(self, content):
562+ # The html element states the namespace and language information.
563+ self.assertEqual(
564+ 'http://www.w3.org/1999/xhtml', content.html['xmlns'])
565+ html_tag = content.html
566+ self.assertEqual('en', html_tag['xml:lang'])
567+ self.assertEqual('en', html_tag['lang'])
568+ self.assertEqual('ltr', html_tag['dir'])
569+
570+ def verify_base_layout_head_parts(self, view, content):
571+ # Verify the common head parts of every layout.
572+ head = content.head
573+ # The page's title starts with the view's page_title.
574+ self.assertTrue(head.title.string.startswith(view.page_title))
575+ # The shortcut icon for the browser chrome is provided.
576+ link_tag = head.link
577+ self.assertEqual('shortcut icon', link_tag['rel'])
578+ self.assertEqual('/@@/launchpad.png', link_tag['href'])
579+ # The template loads the common scripts.
580+ load_script = find_tag_by_id(head, 'base-layout-load-scripts').name
581+ self.assertEqual('script', load_script)
582+
583+ def verify_base_layout_body_parts(self, document):
584+ # Verify the common body parts of every layout.
585+ self.assertEqual('body', document.name)
586+ yui_layout = document.find('div', 'yui-d0')
587+ self.assertTrue(yui_layout is not None)
588+ self.assertEqual(
589+ 'login-logout', yui_layout.find(True, id='locationbar')['class'])
590+ self.assertEqual(
591+ 'yui-main', yui_layout.find(True, id='maincontent')['class'])
592+ self.assertEqual(
593+ 'invisible', document.find(True, id='help-pane')['class'])
594+ self.assertEqual(
595+ 'footer', yui_layout.find(True, id='footer')['class'])
596+
597+ def verify_watermark(self, document):
598+ # Verify the parts of a watermark.
599+ yui_layout = document.find('div', 'yui-d0')
600+ watermark = yui_layout.find(True, id='watermark')
601+ self.assertEqual('watermark-apps-portlet', watermark['class'])
602+ self.assertEqual('/@@/person-logo', watermark.img['src'])
603+ self.assertEqual('Waffles', watermark.h2.string)
604+ self.assertEqual('facetmenu', watermark.ul['class'])
605+
606+ def test_main_side(self):
607+ # The main_side layout has everything.
608+ view = self.makeTemplateView('main_side')
609+ content = BeautifulSoup(view())
610+ self.verify_base_layout_html_element(content)
611+ self.verify_base_layout_head_parts(view, content)
612+ document = find_tag_by_id(content, 'document')
613+ self.verify_base_layout_body_parts(document)
614+ classes = 'tab-overview main_side public yui-skin-sam'.split()
615+ self.assertEqual(classes, document['class'].split())
616+ self.verify_watermark(document)
617+ self.assertEqual(
618+ 'yui-b side', document.find(True, id='side-portlets')['class'])
619+ self.assertEqual('form', document.find(True, id='globalsearch').name)
620+
621+ def test_main_only(self):
622+ # The main_only layout has everything except side portlets.
623+ view = self.makeTemplateView('main_only')
624+ content = BeautifulSoup(view())
625+ self.verify_base_layout_html_element(content)
626+ self.verify_base_layout_head_parts(view, content)
627+ document = find_tag_by_id(content, 'document')
628+ self.verify_base_layout_body_parts(document)
629+ classes = 'tab-overview main_only public yui-skin-sam'.split()
630+ self.assertEqual(classes, document['class'].split())
631+ self.verify_watermark(document)
632+ self.assertEqual(
633+ 'registering', document.find(True, id='registration')['class'])
634+ self.assertEqual(None, document.find(True, id='side-portlets'))
635+ self.assertEqual('form', document.find(True, id='globalsearch').name)
636+
637+ def test_searchless(self):
638+ # The searchless layout is missing side portlets and search.
639+ view = self.makeTemplateView('searchless')
640+ content = BeautifulSoup(view())
641+ self.verify_base_layout_html_element(content)
642+ self.verify_base_layout_head_parts(view, content)
643+ document = find_tag_by_id(content, 'document')
644+ self.verify_base_layout_body_parts(document)
645+ self.verify_watermark(document)
646+ classes = 'tab-overview searchless public yui-skin-sam'.split()
647+ self.assertEqual(classes, document['class'].split())
648+ self.assertEqual(
649+ 'registering', document.find(True, id='registration')['class'])
650+ self.assertEqual(None, document.find(True, id='side-portlets'))
651+ self.assertEqual(None, document.find(True, id='globalsearch'))
652+
653+ def test_locationless(self):
654+ # The locationless layout has no optional content.
655+ view = self.makeTemplateView('locationless')
656+ content = BeautifulSoup(view())
657+ self.verify_base_layout_html_element(content)
658+ self.verify_base_layout_head_parts(view, content)
659+ document = find_tag_by_id(content, 'document')
660+ self.verify_base_layout_body_parts(document)
661+ classes = 'tab-overview locationless public yui-skin-sam'.split()
662+ self.assertEqual(classes, document['class'].split())
663+ self.assertEqual(None, document.find(True, id='registration'))
664+ self.assertEqual(None, document.find(True, id='watermark'))
665+ self.assertEqual(None, document.find(True, id='side-portlets'))
666+ self.assertEqual(None, document.find(True, id='globalsearch'))
667
668=== modified file 'lib/lp/app/templates/base-layout-macros.pt'
669--- lib/lp/app/templates/base-layout-macros.pt 2010-07-14 14:38:07 +0000
670+++ lib/lp/app/templates/base-layout-macros.pt 2010-07-26 13:34:58 +0000
671@@ -288,7 +288,7 @@
672
673 <metal:load-lavascript use-macro="context/@@+base-layout-macros/load-javascript" />
674
675- <script type="text/javascript">
676+ <script id="base-layout-load-scripts" type="text/javascript">
677 LPS.use('node', 'lp', function(Y) {
678 Y.on('load', function(e) {
679 sortables_init();
680
681=== modified file 'lib/lp/app/templates/base-layout.pt'
682--- lib/lp/app/templates/base-layout.pt 2010-07-02 18:35:03 +0000
683+++ lib/lp/app/templates/base-layout.pt 2010-07-26 13:34:58 +0000
684@@ -83,21 +83,22 @@
685 })();
686 </script>
687 <div class="yui-d0">
688- <div id="locationbar">
689+ <div id="locationbar" class="login-logout">
690 <tal:login replace="structure context/@@login_status" />
691 </div><!--id="locationbar"-->
692
693- <div class="watermark-apps-portlet top-portlet"
694+ <div id="watermark" class="watermark-apps-portlet"
695 tal:condition="view/macro:pagehas/applicationtabs">
696- <span tal:replace="structure view/watermark:logo"></span>
697- <h2 tal:replace="structure view/watermark:heading">
698- Celso Providelo
699- </h2>
700- <div id="registration" class="registering">
701- <metal:registering define-slot="registering" />
702- </div>
703- <metal:heading_nav
704- use-macro="context/@@+base-layout-macros/application-buttons"/>
705+ <div class="flowed-block">
706+ <span tal:replace="structure view/watermark:logo"></span>
707+ </div>
708+ <div class="flowed-block wide">
709+ <h2 tal:replace="structure view/watermark:heading">
710+ Celso Providelo
711+ </h2>
712+ <metal:heading_nav
713+ use-macro="context/@@+base-layout-macros/application-buttons"/>
714+ </div>
715 </div>
716
717 <div class="yui-t4"
718@@ -108,13 +109,17 @@
719 lang view/lang|default_language|default;
720 xml:lang view/lang|default_language|default;
721 dir view/dir|string:ltr">
722- <div tal:condition="view/macro:pagehas/applicationtabs">
723+ <div class="context-publication"
724+ tal:condition="view/macro:pagehas/applicationtabs">
725 <h1
726 tal:condition="view/label|nothing"
727 tal:content="view/label"
728 metal:define-slot="heading"
729 >Page Label
730 </h1>
731+ <div id="registration" class="registering">
732+ <metal:registering define-slot="registering" />
733+ </div>
734 <tal:breadcrumbs replace="structure context/@@+hierarchy">
735 ProjectName > Branches > Merge Proposals > fix-for-navigation
736 </tal:breadcrumbs>
737@@ -132,7 +137,7 @@
738 </div><!-- yui-b -->
739 </div><!-- yui-main -->
740
741- <div class="yui-b side"
742+ <div id="side-portlets" class="yui-b side"
743 tal:condition="view/macro:pagehas/portlets">
744 <metal:portlets define-slot="side" />
745 </div><!-- yui-b side -->
746
747=== modified file 'lib/lp/app/templates/launchpad-hierarchy.pt'
748--- lib/lp/app/templates/launchpad-hierarchy.pt 2010-01-22 17:28:32 +0000
749+++ lib/lp/app/templates/launchpad-hierarchy.pt 2010-07-26 13:34:58 +0000
750@@ -3,13 +3,13 @@
751 xmlns:tal="http://xml.zope.org/namespaces/tal"
752 xmlns:metal="http://xml.zope.org/namespaces/metal"
753 xmlns:i18n="http://xml.zope.org/namespaces/i18n"
754- i18n:domain="launchpad">
755+ i18n:domain="launchpad"
756+ tal:condition="view/display_breadcrumbs">
757
758-<tal:breadcrumbs repeat="breadcrumb view/items"
759- condition="view/display_breadcrumbs">
760+<tal:breadcrumbs repeat="breadcrumb view/items">
761 <li>
762 <a tal:attributes="href breadcrumb/url"
763- tal:omit-tag="repeat/breadcrumb/end"><tal:text
764+ tal:omit-tag="repeat/breadcrumb/end"><tal:text
765 condition="not: repeat/breadcrumb/end"
766 replace="breadcrumb/text">
767 Bugs on redfish</tal:text><tal:text
768
769=== modified file 'lib/lp/blueprints/stories/sprints/05-sprint-creation.txt'
770--- lib/lp/blueprints/stories/sprints/05-sprint-creation.txt 2009-11-08 20:09:26 +0000
771+++ lib/lp/blueprints/stories/sprints/05-sprint-creation.txt 2010-07-26 13:34:58 +0000
772@@ -1,137 +1,119 @@
773-= Creating new sprints =
774-
775-We should also be able to create a new sprint. We do this off the Sprints
776-+new page.
777-
778- >>> user_browser.open('http://launchpad.dev/sprints')
779- >>> user_browser.getLink('Register a meeting').click()
780-
781- >>> print user_browser.title
782- Register a meeting...
783+Creating new sprints
784+====================
785+
786+We should also be able to create a new sprint. We do this off the
787+Sprints +new page.
788+
789+ >>> user_browser.open('http://launchpad.dev/sprints')
790+ >>> user_browser.getLink('Register a meeting').click()
791+
792+ >>> print user_browser.title
793+ Register a meeting...
794
795 First we'll test the name field validator.
796
797- >>> user_browser.getControl('Name').value = 'ltsp_on_steroids!'
798- >>> user_browser.getControl('Title').value = 'LTSP On Steroids'
799- >>> summary = 'This is a sprint summary. Some words about the sprint'
800- >>> user_browser.getControl('Summary').value = summary
801- >>> user_browser.getControl('Driver').value = 'kamion'
802- >>> user_browser.getControl('Home Page').value = 'http://www.willy.net'
803- >>> user_browser.getControl('Timezone').value = ['UTC']
804- >>> user_browser.getControl(
805- ... 'Starting Date and Time').value = '10 Oct 2006 09:15'
806- >>> user_browser.getControl(
807- ... 'Finishing Date and Time').value = '13 Oct 2006 16:00'
808- >>> user_browser.getControl('Add Sprint').click()
809-
810- >>> for tag in find_tags_by_class(user_browser.contents, 'message'):
811- ... print tag.renderContents()
812- There is 1 error.
813- <BLANKLINE>
814- Invalid name 'ltsp_on_steroids!'. Names must be at least two characters ...
815-
816-Register a sprint with the same name of a existing one also returns a nice
817-error message
818-
819- >>> user_browser.getControl('Name').value = 'ubz'
820- >>> user_browser.getControl('Add Sprint').click()
821-
822- >>> for tag in find_tags_by_class(user_browser.contents, 'message'):
823- ... print tag.renderContents()
824- There is 1 error.
825- <BLANKLINE>
826- ubz is already in use by another sprint.
827+ >>> user_browser.getControl('Name').value = 'ltsp_on_steroids!'
828+ >>> user_browser.getControl('Title').value = 'LTSP On Steroids'
829+ >>> summary = 'This is a sprint summary. Some words about the sprint'
830+ >>> user_browser.getControl('Summary').value = summary
831+ >>> user_browser.getControl('Driver').value = 'kamion'
832+ >>> user_browser.getControl('Home Page').value = 'http://www.willy.net'
833+ >>> user_browser.getControl('Timezone').value = ['UTC']
834+ >>> user_browser.getControl(
835+ ... 'Starting Date and Time').value = '10 Oct 2006 09:15'
836+ >>> user_browser.getControl(
837+ ... 'Finishing Date and Time').value = '13 Oct 2006 16:00'
838+ >>> user_browser.getControl('Add Sprint').click()
839+
840+ >>> for tag in find_tags_by_class(user_browser.contents, 'message'):
841+ ... print tag.renderContents()
842+ There is 1 error.
843+ <BLANKLINE>
844+ Invalid name 'ltsp_on_steroids!'. Names must be at least two characte...
845+
846+Register a sprint with the same name of a existing one also returns a
847+nice error message
848+
849+ >>> user_browser.getControl('Name').value = 'ubz'
850+ >>> user_browser.getControl('Add Sprint').click()
851+
852+ >>> for tag in find_tags_by_class(user_browser.contents, 'message'):
853+ ... print tag.renderContents()
854+ There is 1 error.
855+ <BLANKLINE>
856+ ubz is already in use by another sprint.
857
858 Create a new sprint with a finish date before the starting date returns
859 a error message.
860
861- >>> user_browser.getControl('Name').value = 'ltsponsteroids'
862- >>> user_browser.getControl(
863- ... 'Starting Date and Time').value = '13 Oct 2006 09:15 '
864- >>> user_browser.getControl(
865- ... 'Finishing Date and Time').value = '10 Oct 2006 16:00'
866- >>> user_browser.getControl('Add Sprint').click()
867+ >>> user_browser.getControl('Name').value = 'ltsponsteroids'
868+ >>> user_browser.getControl(
869+ ... 'Starting Date and Time').value = '13 Oct 2006 09:15 '
870+ >>> user_browser.getControl(
871+ ... 'Finishing Date and Time').value = '10 Oct 2006 16:00'
872+ >>> user_browser.getControl('Add Sprint').click()
873
874- >>> for tag in find_tags_by_class(user_browser.contents, 'message'):
875- ... print tag.renderContents()
876- There is 1 error.
877- <BLANKLINE>
878- This event can't start after it ends
879+ >>> for tag in find_tags_by_class(user_browser.contents, 'message'):
880+ ... print tag.renderContents()
881+ There is 1 error.
882+ <BLANKLINE>
883+ This event can't start after it ends
884
885 Also, the date is now presented in a canonicalised format, with time in
886 minutes rather than second-level accuracy:
887
888- >>> user_browser.getControl('Starting Date and Time').value
889- '2006-10-13 09:15'
890- >>> user_browser.getControl('Finishing Date and Time').value
891- '2006-10-10 16:00'
892-
893-Fix the date and try again. We're redirected to the sprint home page
894-for the new sprint.
895-
896- >>> user_browser.getControl(
897- ... 'Starting Date and Time').value = '10 Oct 2006 09:15 '
898- >>> user_browser.getControl(
899- ... 'Finishing Date and Time').value = '13 Oct 2006 16:00'
900- >>> user_browser.getControl('Add Sprint').click()
901-
902- >>> user_browser.url
903- 'http://launchpad.dev/sprints/ltsponsteroids'
904-
905-Because sprints are root contexts, an h1 heading is used to display
906-the main heading above the application buttons) and the breadcrumbs
907-are not displayed.
908-
909- >>> print_location(user_browser.contents)
910- Hierarchy: None displayed
911- Tabs:
912- * Overview (selected) - not linked
913- ...
914- * Bugs - not linked
915- * Blueprints - http://blueprints.launchpad.dev/sprints/ltsponsteroids
916- * Translations - not linked
917- * Answers - not linked
918- Main heading: LTSP On Steroids
919-
920-There is no h1 heading in the main content.
921-
922- >>> print find_main_content(user_browser.contents).find('h1')
923- None
924+ >>> user_browser.getControl('Starting Date and Time').value
925+ '2006-10-13 09:15'
926+
927+ >>> user_browser.getControl('Finishing Date and Time').value
928+ '2006-10-10 16:00'
929+
930+Fix the date and try again. We're redirected to the sprint home page for
931+the new sprint.
932+
933+ >>> user_browser.getControl(
934+ ... 'Starting Date and Time').value = '10 Oct 2006 09:15 '
935+ >>> user_browser.getControl(
936+ ... 'Finishing Date and Time').value = '13 Oct 2006 16:00'
937+ >>> user_browser.getControl('Add Sprint').click()
938+
939+ >>> user_browser.url
940+ 'http://launchpad.dev/sprints/ltsponsteroids'
941
942 Since the sprint's time zone was set to UTC, the dates are displayed in
943 that time zone:
944
945- >>> print extract_text(find_tag_by_id(user_browser.contents, 'start-end'))
946- Starts: 09:15 UTC on Tuesday, 2006-10-10
947- Ends: 16:00 UTC on Friday, 2006-10-13
948-
949-Because this is a brand new sprint, it will have no specs, and we should see
950-a warning to that effect on the page.
951-
952- >>> message = 'Nobody has yet proposed any blueprints for discussion'
953- >>> message in user_browser.contents
954- True
955-
956+ >>> print extract_text(find_tag_by_id(user_browser.contents, 'start-end'))
957+ Starts: 09:15 UTC on Tuesday, 2006-10-10
958+ Ends: 16:00 UTC on Friday, 2006-10-13
959+
960+Because this is a brand new sprint, it will have no specs, and we should
961+see a warning to that effect on the page.
962+
963+ >>> message = 'Nobody has yet proposed any blueprints for discussion'
964+ >>> message in user_browser.contents
965+ True
966
967 Add a new sprint with a different time zone is also handled correctly.
968
969- >>> user_browser.open('http://launchpad.dev/sprints/+new')
970- >>> user_browser.getControl('Name').value = 'africa-sprint'
971- >>> user_browser.getControl('Title').value = 'Africa Sprint'
972- >>> summary = 'This is a sprint summary. Some words about the sprint'
973- >>> user_browser.getControl('Summary').value = summary
974- >>> user_browser.getControl('Home Page').value = 'http://www.ubuntu.com'
975- >>> user_browser.getControl('Timezone').value = ['Africa/Johannesburg']
976- >>> user_browser.getControl(
977- ... 'Starting Date and Time').value = '10 Jul 2006 09:15'
978- >>> user_browser.getControl(
979- ... 'Finishing Date and Time').value = '13 Jul 2006 16:00'
980- >>> user_browser.getControl('Add Sprint').click()
981-
982- >>> user_browser.url
983- 'http://launchpad.dev/sprints/africa-sprint'
984-
985- >>> print extract_text(find_tag_by_id(user_browser.contents, 'start-end'))
986- Starts: 09:15 SAST on Monday, 2006-07-10
987- Ends: 16:00 SAST on Thursday, 2006-07-13
988+ >>> user_browser.open('http://launchpad.dev/sprints/+new')
989+ >>> user_browser.getControl('Name').value = 'africa-sprint'
990+ >>> user_browser.getControl('Title').value = 'Africa Sprint'
991+ >>> summary = 'This is a sprint summary. Some words about the sprint'
992+ >>> user_browser.getControl('Summary').value = summary
993+ >>> user_browser.getControl('Home Page').value = 'http://www.ubuntu.com'
994+ >>> user_browser.getControl('Timezone').value = ['Africa/Johannesburg']
995+ >>> user_browser.getControl(
996+ ... 'Starting Date and Time').value = '10 Jul 2006 09:15'
997+ >>> user_browser.getControl(
998+ ... 'Finishing Date and Time').value = '13 Jul 2006 16:00'
999+ >>> user_browser.getControl('Add Sprint').click()
1000+
1001+ >>> user_browser.url
1002+ 'http://launchpad.dev/sprints/africa-sprint'
1003+
1004+ >>> print extract_text(find_tag_by_id(user_browser.contents, 'start-end'))
1005+ Starts: 09:15 SAST on Monday, 2006-07-10
1006+ Ends: 16:00 SAST on Thursday, 2006-07-13
1007+
1008
1009
1010=== modified file 'lib/lp/bugs/stories/bugs/xx-bug-heat-on-bug-page.txt'
1011--- lib/lp/bugs/stories/bugs/xx-bug-heat-on-bug-page.txt 2010-03-15 13:22:02 +0000
1012+++ lib/lp/bugs/stories/bugs/xx-bug-heat-on-bug-page.txt 2010-07-26 13:34:58 +0000
1013@@ -3,8 +3,8 @@
1014 The bug heat appears on the bug index page as four flames.
1015
1016 >>> anon_browser.open('http://bugs.launchpad.dev/firefox/+bug/1')
1017- >>> print find_tag_by_id(
1018- ... anon_browser.contents, 'registration').fetch('img')[0]
1019+ >>> print find_main_content(
1020+ ... anon_browser.contents).find('img', src='/@@/bug-heat-0.png')
1021 <img src="/@@/bug-heat-0.png" alt="0 out of 4 heat flames" title="Heat: 0" />
1022
1023 Packages use their distribution as context when generating flame icons
1024@@ -30,6 +30,6 @@
1025 >>> logout()
1026
1027 >>> anon_browser.open(canonical_url(dsp_bug))
1028- >>> print find_tag_by_id(
1029- ... anon_browser.contents, 'registration').fetch('img')[0]
1030+ >>> print find_main_content(
1031+ ... anon_browser.contents).find('img', src='/@@/bug-heat-0.png')
1032 <img src="/@@/bug-heat-0.png" alt="0 out of 4 heat flames" title="Heat: 1250" />
1033
1034=== modified file 'lib/lp/bugs/stories/bugs/xx-malone-homepage.txt'
1035--- lib/lp/bugs/stories/bugs/xx-malone-homepage.txt 2010-05-19 05:47:50 +0000
1036+++ lib/lp/bugs/stories/bugs/xx-malone-homepage.txt 2010-07-26 13:34:58 +0000
1037@@ -1,26 +1,18 @@
1038 Say hello to Bugs. :)
1039
1040- >>> browser.open('http://bugs.launchpad.dev/')
1041- >>> browser.url
1042- 'http://bugs.launchpad.dev/'
1043+ >>> browser.open('http://bugs.launchpad.dev/')
1044+ >>> browser.url
1045+ 'http://bugs.launchpad.dev/'
1046
1047- >>> print_location(browser.contents)
1048- Hierarchy: None displayed
1049- Tabs:
1050- * Launchpad Home - http://launchpad.dev/
1051- * Code - http://code.launchpad.dev/
1052- * Bugs (selected) - http://bugs.launchpad.dev/
1053- * Blueprints - http://blueprints.launchpad.dev/
1054- * Translations - http://translations.launchpad.dev/
1055- * Answers - http://answers.launchpad.dev/
1056- Main heading: Bug tracking
1057+ >>> print browser.title
1058+ Bug tracking
1059
1060 There are a few related pages linked in a portlet:
1061
1062- >>> related_pages = find_portlet(browser.contents, 'Related pages')
1063- >>> for link in related_pages.findAll('a'):
1064- ... print "%s\n --> %s" % (extract_text(link), link.get('href'))
1065- Bug trackers
1066+ >>> related_pages = find_portlet(browser.contents, 'Related pages')
1067+ >>> for link in related_pages.findAll('a'):
1068+ ... print "%s\n --> %s" % (extract_text(link), link.get('href'))
1069+ Bug trackers
1070 --> http://bugs.launchpad.dev/bugs/bugtrackers
1071- CVE tracker
1072+ CVE tracker
1073 --> http://bugs.launchpad.dev/bugs/cve
1074
1075=== modified file 'lib/lp/bugs/stories/bugtracker/bugtrackers-index.txt'
1076--- lib/lp/bugs/stories/bugtracker/bugtrackers-index.txt 2010-06-16 15:28:35 +0000
1077+++ lib/lp/bugs/stories/bugtracker/bugtrackers-index.txt 2010-07-26 13:34:58 +0000
1078@@ -1,19 +1,12 @@
1079-= Bug trackers in Launchpad =
1080+Bug trackers in Launchpad
1081+=========================
1082
1083 The bug trackers index page has the same navigation as the main Bugs
1084 page, with the addition of a breadcrumb itself.
1085
1086 >>> user_browser.open('http://launchpad.dev/bugs/bugtrackers')
1087- >>> print_location(user_browser.contents)
1088- Hierarchy: None displayed
1089- Tabs:
1090- * Launchpad Home - http://launchpad.dev/
1091- * Code - http://code.launchpad.dev/
1092- * Bugs (selected) - http://bugs.launchpad.dev/
1093- * Blueprints - http://blueprints.launchpad.dev/
1094- * Translations - http://translations.launchpad.dev/
1095- * Answers - http://answers.launchpad.dev/
1096- Main heading: Bug trackers registered in Launchpad
1097+ >>> print user_browser.title
1098+ Bug trackers registered in Launchpad
1099
1100 The page presents a table with all bugtrackers currently registered:
1101
1102
1103=== modified file 'lib/lp/bugs/stories/cve/cve-pages.txt'
1104--- lib/lp/bugs/stories/cve/cve-pages.txt 2010-05-21 13:49:36 +0000
1105+++ lib/lp/bugs/stories/cve/cve-pages.txt 2010-07-26 13:34:58 +0000
1106@@ -1,4 +1,5 @@
1107-= CVE tracking =
1108+CVE tracking
1109+============
1110
1111 Launchpad supports the CVE database system.
1112
1113@@ -17,16 +18,8 @@
1114 >>> browser.getLink('CVE entries').click()
1115 >>> print browser.url
1116 http://bugs.launchpad.dev/bugs/cve
1117- >>> print_location(browser.contents)
1118- Hierarchy: None displayed
1119- Tabs:
1120- * Launchpad Home - http://launchpad.dev/
1121- * Code - http://code.launchpad.dev/
1122- * Bugs (selected) - http://bugs.launchpad.dev/
1123- * Blueprints - http://blueprints.launchpad.dev/
1124- * Translations - http://translations.launchpad.dev/
1125- * Answers - http://answers.launchpad.dev/
1126- Main heading: Launchpad CVE tracker
1127+ >>> print browser.title
1128+ Launchpad CVE tracker
1129 >>> print browser.contents
1130 <...
1131 ...CVE-2005-2737 (Candidate)...
1132@@ -37,76 +30,58 @@
1133 >>> browser.getLink('Show all registered CVEs').click()
1134 >>> print browser.url
1135 http://bugs.launchpad.dev/bugs/cve/+all
1136- >>> print_location(browser.contents)
1137- Hierarchy: None displayed
1138- Tabs:
1139- * Launchpad Home - http://launchpad.dev/
1140- * Code - http://code.launchpad.dev/
1141- * Bugs (selected) - http://bugs.launchpad.dev/
1142- * Blueprints - http://blueprints.launchpad.dev/
1143- * Translations - http://translations.launchpad.dev/
1144- * Answers - http://answers.launchpad.dev/
1145- Main heading: Launchpad CVE tracker
1146+ >>> print browser.title
1147+ Launchpad CVE tracker
1148
1149 Now, we will test the search functionality of the CVE tracker. First we
1150 will search for the word "loss" in the database and see how many show
1151 up. We expect to see CVE-1999-2345.
1152
1153- >>> print http(r"""
1154- ... POST /bugs/cve/ HTTP/1.1
1155- ... Content-Length: 9
1156- ... Content-Type: application/x-www-form-urlencoded
1157- ...
1158- ... text=loss""")
1159- HTTP/1.1 200 Ok
1160- ...Matches: 1...
1161- ...CVE-1999-2345 (Candidate)...
1162+ >>> print http(r"""
1163+ ... POST /bugs/cve/ HTTP/1.1
1164+ ... Content-Length: 9
1165+ ... Content-Type: application/x-www-form-urlencoded
1166+ ...
1167+ ... text=loss""")
1168+ HTTP/1.1 200 Ok
1169+ ...Matches: 1...
1170+ ...CVE-1999-2345 (Candidate)...
1171
1172
1173 Finally, let's put a CVE number in there. We expect to jump straight to that
1174 CVE.
1175
1176- >>> print http(r"""
1177- ... POST /bugs/cve/ HTTP/1.1
1178- ... Content-Length: 14
1179- ... Content-Type: application/x-www-form-urlencoded
1180- ...
1181- ... text=2005-2737""")
1182- HTTP/1.1 303 See Other
1183- ...
1184- Location: http://.../bugs/cve/2005-2737
1185- ...
1186+ >>> print http(r"""
1187+ ... POST /bugs/cve/ HTTP/1.1
1188+ ... Content-Length: 14
1189+ ... Content-Type: application/x-www-form-urlencoded
1190+ ...
1191+ ... text=2005-2737""")
1192+ HTTP/1.1 303 See Other
1193+ ...
1194+ Location: http://.../bugs/cve/2005-2737
1195+ ...
1196
1197-A CVE page has the same navigation as the Bugs front page (because CVEs
1198-don't have their own blueprints, translations, etc), and also includes
1199-a link back to the main CVE page.
1200+A CVE page includes a link back to the main CVE page.
1201
1202 >>> anon_browser.open('http://launchpad.dev/bugs/cve/2005-2737')
1203- >>> print_location(anon_browser.contents)
1204- Hierarchy: None displayed
1205- Tabs:
1206- * Launchpad Home - http://launchpad.dev/
1207- * Code - http://code.launchpad.dev/
1208- * Bugs (selected) - http://bugs.launchpad.dev/
1209- * Blueprints - http://blueprints.launchpad.dev/
1210- * Translations - http://translations.launchpad.dev/
1211- * Answers - http://answers.launchpad.dev/
1212- Main heading: CVE 2005-2737
1213+ >>> print anon_browser.title
1214+ CVE 2005-2737 : ...
1215 >>> back_link = anon_browser.getLink('Launchpad CVE tracker')
1216 >>> print back_link.url
1217 http://launchpad.dev/bugs/cve
1218
1219 The CVE page links to the related bugs.
1220
1221- >>> "Cross-site scripting (XSS) vulnerability" in anon_browser.contents
1222- True
1223- >>> 'No related bugs' in anon_browser.contents
1224- True
1225- >>> for tag in find_tags_by_class(
1226- ... anon_browser.contents, 'menu-link-linkbug'):
1227- ... print tag
1228- <a href="+linkbug" class="menu-link-linkbug sprite add">Link to bug</a>
1229- >>> 'Candidate' in anon_browser.contents
1230- True
1231- >>> 'http://marc.theaimsgroup.com/?l=bugtraq&amp;m=112511025414488&amp;w=2">20050826 Multiple PHP' in anon_browser.contents
1232- True
1233+ >>> "Cross-site scripting (XSS) vulnerability" in anon_browser.contents
1234+ True
1235+ >>> 'No related bugs' in anon_browser.contents
1236+ True
1237+ >>> for tag in find_tags_by_class(
1238+ ... anon_browser.contents, 'menu-link-linkbug'):
1239+ ... print tag
1240+ <a href="+linkbug" class="menu-link-linkbug sprite add">Link to bug</a>
1241+ >>> 'Candidate' in anon_browser.contents
1242+ True
1243+ >>> '20050826 Multiple PHP' in anon_browser.contents
1244+ True
1245
1246=== modified file 'lib/lp/bugs/templates/bugtask-index.pt'
1247--- lib/lp/bugs/templates/bugtask-index.pt 2010-07-16 16:14:39 +0000
1248+++ lib/lp/bugs/templates/bugtask-index.pt 2010-07-26 13:34:58 +0000
1249@@ -43,12 +43,11 @@
1250 </metal:side>
1251
1252 <tal:registering metal:fill-slot="registering">
1253- Bug #<tal:block content="context/bug/id"/> reported by
1254+ Reported by
1255 <tal:reporter replace="structure context/bug/owner/fmt:link" />
1256 <span
1257 tal:attributes="title context/bug/datecreated/fmt:datetime"
1258 tal:content="context/bug/datecreated/fmt:displaydate" />
1259- <tal:heat replace="structure view/bug_heat_html" />
1260 </tal:registering>
1261
1262 <metal:heading fill-slot="heading" tal:define="context_menu context/menu:context">
1263@@ -92,6 +91,10 @@
1264 </tal:XXX>
1265 </p>
1266
1267+ <div style="float: right;">
1268+ <tal:heat replace="structure view/bug_heat_html" />
1269+ </div>
1270+
1271 <div tal:replace="structure context/bug/@@+bugtasks-and-nominations-table" />
1272
1273 <div id="maincontentsub">